2016-11-17 2 views
0

フロントエンドのUIには、バックエンドで数百万行以上のフィルタリングシステムがあります。これは、ロジックの過程で構築されたIQueryableを使用し、一度にすべて実行されます。それぞれの個々のUIコンポーネントはANDで結ばれます(たとえば、Dropdown1とDropdown2は共通に選択されているものの両方を持つ行のみを返します)。これは問題ではありません。ただし、Dropdown3には2つのタイプのデータがあり、チェックされたアイテムは一緒にORされ、残りのクエリとANDされる必要があります。Linqクエリのタイムアウト、どのように合理化クエリ

大量の行が処理されているため、タイムアウトが発生します。起こる必要があるいくつかの追加の結合があるので、ややこしい。ここに私のコードは、テーブル名を交換して、次のとおりです。

//The end list has driver ids in it--but the data comes from two different places. Build a list of all the driver ids. 
driverIds = db.CarDriversManyToManyTable.Where(
         cd => 
          filter.CarIds.Contains(cd.CarId) && //get driver IDs for each car ID listed in filter object 
          ).Select(cd => cd.DriverId).Distinct().ToList(); 

driverIds = driverIds.Concat(
        db.DriverShopManyToManyTable.Where(ds => filter.ShopIds.Contains(ds.ShopId)) //Get driver IDs for each Shop listed in filter object 
         .Select(ds => ds.DriverId) 
         .Distinct()).Distinct().ToList(); 
//Now we have a list solely of driver IDs 

//The query operates over the Driver table. The query is built up like this for each item in the UI. Changing from Linq is not an option. 
query = query.Where(d => driverIds.Contains(d.Id)); 

どのように私は私がメモリにIDの数千人と数千人を取得する必要がないように、その後、このクエリを合理SQLにそれらをフィードバックすることができますか?

答えて

3

単一のSQLクエリを作成する方法はいくつかあります。タイプがIQueryable<T>のクエリの部分を保持するために必要なもの、つまりToListToArrayAsEnumerableなどの方法を使用して強制的に実行し、メモリ内で評価する必要はありません。

一つの方法は、(定義によってユニークになります)フィルタIDを含むUnionクエリを作成し、メインクエリにそれを適用するjoin演算子を使用することです:

var driverIdFilter1 = db.CarDriversManyToManyTable 
    .Where(cd => filter.CarIds.Contains(cd.CarId)) 
    .Select(cd => cd.DriverId); 
var driverIdFilter2 = db.DriverShopManyToManyTable 
    .Where(ds => filter.ShopIds.Contains(ds.ShopId)) 
    .Select(ds => ds.DriverId); 
var driverIdFilter = driverIdFilter1.Union(driverIdFilter2); 
query = query.Join(driverIdFilter, d => d.Id, id => id, (d, id) => d); 

もう一つの方法は、2 OR-を使用することができあなたがしようとすると、より良い実行する1見ることができました

query = query.Where(d => 
    db.CarDriversManyToManyTable.Any(cd => d.Id == cd.DriverId && filter.CarIds.Contains(cd.CarId)) 
    || 
    db.DriverShopManyToManyTable.Any(ds => d.Id == ds.DriverId && filter.ShopIds.Contains(ds.ShopId)) 
); 

EXISTS(...) OR EXISTS(...) SQLクエリフィルタに変換するでしょうAnyベースの条件を、エド。

1

この質問に対する回答は複雑で、個々のケースで個別に役立つ可能性があります。

まず、ページネーションの使用を検討してください。 .Skip(PageNum * PageSize).Take(PageSize)私はあなたのユーザーがフロントエンドで一度に何百万もの行を見る必要があるとは思っていません。それらを100だけ表示するか、他の小さな番号があなたに合理的と思われるものを表示してください。

必要なデータを取得するために結合を使用する必要があると述べました。これらの結合は、メモリ内ではなく(オブジェクトへのlinq)、IQueryable(エンティティフレームワーク)を形成しながら行うことができます。 linqの結合構文を読んでください。

HOWEVER - LINQで明示的な結合を実行することは、特にデータベースを自分で設計する場合はベストプラクティスではありません。エンティティの第1世代のデータベースを作成する場合は、テーブルに外部キー制約を設定することを検討してください。これにより、データベースの最初のエンティティの生成がそれらを選択してナビゲーションプロパティを提供し、コードを大幅に簡素化します。

ただし、データベース設計に対して何も支配力や影響力がない場合は、まずSQLでクエリを構築してそのパフォーマンスを確認することをお勧めします。望ましいパフォーマンスが得られるまで最適化し、明示的な結合を最後の手段として使用するエンティティフレームワークのlinqクエリに変換します。

このようなクエリを高速化するには、参加しているすべての「キー」列に対してインデックスを作成する必要があります。パフォーマンスを向上させるために必要なインデックスを把握するには、EF linqで生成されたSQLクエリを取得し、SQL Server Management Studioに持ってきてください。そこから、生成されたSQLを更新して、@ pパラメータのいくつかの事前定義済みの値を提供して、例を作成してください。これを実行したら、クエリを右クリックして、見積もり実行計画を表示するか、実際の実行計画を含めるかを選択します。索引付けが問合せのパフォーマンスを向上させることができる場合、この機能によってそのことがわかり、必要な索引を作成するためのスクリプトを提供する可能性もあります。

1

LINQ拡張のインスタンスバージョンを使用すると、完了する前にいくつかのコレクションが作成されているように見えます。 fromステートメントのバージョンを使用するとかなり減ります。

driveIds = (from var record in db.CarDriversManyToManyTable 
      where filter.CarIds.Contains(record.CarId) 
      select record.DriverId).Concat 
      (from var record in db.DriverShopManyToManyTable 
      where filter.ShopIds.Contains(record.ShopId) 
      select record.DriverId).Distinct() 

また、groupby拡張を使用すると、各ドライバIDを照会するよりもパフォーマンスが向上します。