2016-04-13 2 views
0

Ich benutze Entity Framework und häufig in Problem, wo ich eine große Anzahl von Datensätzen durchlaufen möchten. Mein Problem ist, dass wenn ich alle auf einmal ziehe, ich eine Auszeit riskiere; Wenn ich eins nach dem anderen ziehe, wird buchstäblich jeder einzelne Datensatz eine separate Abfrage sein und es dauert ewig.IEnumerable Erweiterung, um Ergebnisse in Batches zu ziehen

Ich möchte eine Linq-Erweiterung implementieren, die die Ergebnisse in Batches zieht, aber immer noch als IEnumerable verwendet werden kann. Ich würde ihm eine Reihe von Schlüsseln geben (höchstwahrscheinlich die primären IDs aller Datensätze, die ich ziehe), eine Stapelgröße (höher für einfache Objekte, niedriger für komplexe Objekte) und Func, die definiert, wie ein Schlüsselsatz angewendet wird zu einem Datensatzsatz T. Ich würde es so nennen:

//get the list of items to pull--in this case, a set of order numbers 
List<int> orderNumbers = GetOrderNumbers(); 

//set the batch size 
int batchSize = 100; 

//loop through the set using BatchedSelector extension. Note the selection 
//function at the end which allows me to 
foreach (var order in dbContext.Orders.BatchedSelector(repairNumbers, batchSize, (o, k) => k.Contains(o.OrderNumber))) 
{ 
    //do things 
} 

Hier ist mein Entwurf Lösung:

/// <summary> 
    /// A Linq extension that fetches IEnumerable results in batches, aggregating queries 
    /// to improve EF performance. Operates transparently to application and acts like any 
    /// other IEnumerable. 
    /// </summary> 
    /// <typeparam name="T">Header record type</typeparam> 
    /// <param name="source">Full set of records</param> 
    /// <param name="keys">The set of keys that represent specific records to pull</param> 
    /// <param name="selector">Function that filters the result set to only those which match the key set</param> 
    /// /// <param name="maxBatchSize">Maximum number of records to pull in one query</param> 
    /// <returns></returns> 
    public static IEnumerable<T> BatchedSelector<T>(this IEnumerable<T> source, IEnumerable<int> keys, Func<T, IEnumerable<int>, bool> selector, int maxBatchSize) 
    { 
     //the index of the next key (or set of keys) to process--we start at 0 of course 
     int currentKeyIndex = 0;    

     //to provide some resiliance, we will allow the batch size to decrease if we encounter errors 
     int currentBatchSize = maxBatchSize; 
     int batchDecreaseAmount = Math.Max(1, maxBatchSize/10); //10%, but at least 1 

     //other starting variables; a list to hold results and the associated batch of keys 
     List<T> resultList = null; 
     IEnumerable<int> keyBatch = null; 

     //while there are still keys remaining, grab the next set of keys 
     while ((keyBatch = keys.Skip(currentKeyIndex).Take(currentBatchSize)).Count() > 0) 
     { 
      //try to fetch the results 
      try 
      { 
       resultList = source.Where(o => selector(o, keyBatch)).ToList(); // <-- this is where errors occur 
       currentKeyIndex += maxBatchSize; //increment key index to mark these keys as processed 
      } 
      catch 
      { 
       //decrease the batch size for our retry 
       currentBatchSize -= batchDecreaseAmount; 

       //if we've run out of batch overhead, throw the error 
       if (currentBatchSize <= 0) throw; 

       //otherwise, restart the loop 
       continue; 
      } 

      //since we've successfully gotten the set of keys, yield the results 
      foreach (var match in resultList) yield return match; 
     } 

     //the loop is over; we're done 
     yield break; 
    } 

Aus irgendeinem Grund die „where“ -Klausel hat keine Wirkung. Ich habe validiert, dass die richtigen Schlüssel in keyBatch sind, aber die erwartete WHERE OrderNumber IN (k1, k2, k3, kn) Zeile ist nicht da. Es ist so, als hätte ich die Wo-Aussage überhaupt nicht.

Meine beste Vermutung ist, dass ich den Ausdruck erstellen und kompilieren muss, aber ich bin mir nicht sicher, ob das das Problem ist und ich bin mir nicht sicher, wie ich es beheben soll. Würde jeden Input lieben. Vielen Dank!

Antwort

0

Erstens, danke Arturo. Sie haben mich für diese Lösung auf den richtigen Weg gebracht. Ich ging davon aus, dass es sich um ein Linq-> Entity-Thema handelte, aber diese Probleme sind für mich noch immer nicht intuitiv zu lösen.

Zweitens habe ich stark ausgeliehen von Shimmy 's Antwort auf this question. Danke Shimmy!

Zuerst habe ich die Methode aktualisiert, um andere Schlüsseltypen als Ganzzahlen zu unterstützen, denn warum nicht. So ist die Methode Unterschrift nun (beachten Sie die Änderung zu IQueryable Quelle):

public static IEnumerable<T> BatchedSelector<T, TKey>(this IQueryable<T> source, Expression<Func<T, TKey>> selector, IEnumerable<TKey> keys, int maxBatchSize) 

Das Verfahren blieb im wesentlichen die gleich andere als die Linie, die Fehler produzieren, die nun mit Fassung:

resultList = source.WhereIn(selector, keyBatch).ToList(); 

WhereIn ist eine Erweiterung Linq meist von Shimmy entlehnt:

public static IQueryable<T> WhereIn<T, TKey>(this IQueryable<T> source, Expression<Func<T, TKey>> selector, IEnumerable<TKey> keyCollection) 
    { 
     if (selector == null) throw new ArgumentNullException("Null selector"); 
     if (keyCollection == null) throw new ArgumentNullException("Null collection"); 

     //if no items in collection, no results 
     if (!keyCollection.Any()) return source.Where(t => false); 

     //assemble expression 
     var p = selector.Parameters.Single(); 
     var equals = keyCollection.Select(value => (Expression)Expression.Equal(selector.Body, Expression.Constant(value, typeof(TKey)))); 
     var body = equals.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); 

     //return expression 
     return source.Where(Expression.Lambda<Func<T, bool>>(body, p)); 
    } 

Das hat mich gelehrt, etwas ziemlich cool: wenn Sie eine where-Klausel einer Reihe von konstanten füttern Vergleiche, wird es in eine SQL In Anweisung umgewandelt werden! Ordentlich!

Mit diesen Änderungen produziert die Methode Ergebnisse schnell und einfach.

1

Where, Skip, Take und alle diese Art von Methoden sind Erweiterungen Methoden, keine Mitglieder von IEnumerable<T>. Für alle diese Methoden sind eigentlich 2 Versionen, eine für IEnumerable<> und eine für IQueryable<>.

Enumerable Erweiterungen

  • Where(Func<TSource, bool> predicate)
  • Select(Func<TSource, TResult> selector)

Abfragbare Erweiterungen

  • Where(Expression<Func<TSource, bool>> predicate)
  • Select(Expression<Func<TSource, TResult>> predicate)

Wie Sie den Unterschied sehen kann, ist, dass Queryable Erweiterungen anstelle einer direkten Delegierten einen Expression<> nehmen. Dieser Ausdruck ermöglicht EF Ihren Code in SQL zu transformieren.

Da Sie Ihre Variablen/Parameter in BatchedSelector() Methode als IEnumerable<> deklarieren Ihre verwenden die Erweiterungen in Enumerable Klasse, und diese Erweiterungen werden im Speicher ausgeführt.

ist ein häufiger Fehler denkt, dass zu Polymorphismus durch ein DbSet (IQueryable<>) unabhängig davon, ob Sie es als IEnumerable<> die Abfragen zu SQL übersetzt werden verwenden, ist dies für die richtigen Mitglieder nur wahr, aber nicht für die Erweiterung Methoden.

Ihr Code kann geändert werden, indem Sie Ihre IEnumerable<> Variablen/Parameter in IQueryable<> ändern.

Sie können mehr über die Unterschiede zwischen IEnumerable und IQueryablehere lesen.

Verwandte Themen