2016-04-13 10 views
0

私はEntity Frameworkを使用しており、多数のレコードを繰り返し処理する問題に頻繁に対応しています。私の問題は、それらを一気に取り上げると、私はタイムアウトになる危険があるということです。一度に1つずつ取り出すと、文字通りすべてのレコードが個別のクエリになり、永遠にかかるようになります。バッチで結果を取得するためのIEnumerable拡張

結果をバッチで取得するLinq拡張を実装したいが、それでもIEnumerableとして使用できる。私はそれにキーのセット(私は引っ張っているすべてのレコードのプライマリID)、バッチサイズ(単純なオブジェクトの場合は高く、複雑なオブジェクトの場合は低い)、およびキーのセットを適用する方法を定義するFuncレコードタイプTのセットに変換します。私はこのようにそれを呼び出します。

//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 
} 

は、ここに私のドラフトソリューションです:

/// <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; 
    } 

何らかの理由で、「どこで」節は効果がありません。正しいキーがkeyBatchにあることを確認しましたが、期待されるWHERE OrderNumber IN (k1, k2, k3, kn)行はありません。あたかもwhereステートメントがまったくないかのようです。

私は、式を作成してコンパイルする必要があると思っていますが、それが問題なのかどうかは分かりませんし、修正する方法もわかりません。入力が大好きです。ありがとう!

答えて

0

まず、Arturoに感謝します。あなたはこの解決策のために正しい道を私に設定しました。私はそれがLinq-> Entityの問題であると考えて行ったが、これらの問題は私が解決するために直感的ではありません。

第2に、私はShimmyの回答this questionから大きく借りました。ありがとうShimmy!

まず、整数以外のキータイプをサポートするためのメソッドを更新しました。理由はないからです。そうメソッドシグネチャは(のIQueryableソースへの変更に注意してください)、次のようになる:

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

方法は、実質的に今で置換されたエラーを生成したラインと同じ他の滞在:

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

WhereInはほとんどシミーから借りLINQの拡張です:

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)); 
    } 

これは私にはかなりクールな何かを教えて:あなたは、一定の束のwhere句を養う場合比較すると、SQL In文に変換されます。ニート!

これらの変更により、この方法は迅速かつ容易に結果を生成します。

1

Where,Skip,Takeであり、この種の方法はすべて、拡張メソッドであり、IEnumerable<T>のメンバーではありません。これらのメソッドはすべて実際には2つのバージョン、IEnumerable<>IQueryable<>の1つです。

可算拡張

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

照会可能拡張子

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

あなたは違いを見ることができるようにQueryable拡張子ではなく、直接デリゲートのExpression<>を取るということです。これらの式は、EFがコードをSQLに変換するものです。

変数/パラメータをBatchedSelector()という方法で宣言しているので、という名前の拡張子はEnumerableクラスにあり、この拡張子はメモリ内で実行されます。

よくある間違いは、あなたがIEnumerable<>としてそれを使用する場合、クエリはSQLに変換されますに関係なく、これが唯一の適切なメンバーのためではなく、拡張のために真であるため、多型、DbSetIQueryable<>)だと思いますですメソッド。

IEnumerable<>の変数/パラメータをIQueryable<>に変更してコードを修正することができます。

IEnumerableIQueryablehereの相違点については、こちらをご覧ください。

関連する問題