2017-01-10 12 views
10

'Top'オブジェクトに0〜N 'Sub'オブジェクトがあるデータモデルがあります。 SQLでは、これは外部キーdbo.Sub.TopIdによって実現されます。EFコアネストされたLinq selectの結果がN + 1個のSQLクエリ

このLINQクエリは、 単一 SQLクエリに変換される(オフ遅延ロードして)Entity Frameworkは6で
var query = context.Top 
    //.Include(t => t.Sub) Doesn't seem to do anything 
    .Select(t => new { 
     prop1 = t.C1, 
     prop2 = t.Sub.Select(s => new { 
      prop21 = s.C3 //C3 is a column in the table 'Sub' 
     }) 
     //.ToArray() results in N + 1 queries 
    }); 
var res = query.ToArray(); 

。結果は完全にロードされるので、res[0].prop2は既に埋め込まれたIEnumerable<SomeAnonymousType>になります。

サブコレクションがまだロードされず、型であるがEntityFrameworkCore(NuGet v1.1.0デベロッパー)を使用して:

System.Linq.Enumerable.WhereSelectEnumerableIterator<Microsoft.EntityFrameworkCore.Storage.ValueBuffer, <>f__AnonymousType1<string>>. 

をあなたはそれを反復処理するまでデータがN +で、その結果、ロードされません1クエリ。私が.ToArray()をコメントに追加すると、データはvar resに完全にロードされますが、SQLプロファイラを使用するとこれは1つのSQLクエリーでこれ以上達成されません。各 'Top'オブジェクトに対して、 'Sub'テーブルのクエリが実行されます。

最初に指定する.Include(t => t.Sub)は何も変更されていないようです。匿名型の使用は問題ではないようですが、new { ... }ブロックをnew MyPocoClass { ... }に置き換えても何も変わりません。

私の質問はすべてのデータがすぐに読み込まれるEF6と同様の動作をする方法はありますか?


:私はこの例では、問題はそうのようなクエリを実行後にメモリに匿名のオブジェクトを作成することによって固定することができますことを実現:

var query2 = context.Top 
    .Include(t => t.Sub) 
    .ToArray() 
    .Select(t => new //... select what is needed, fill anonymous types 

しかしこれはただのですAutoMapperが私のプロジェクトでDTOを埋めるためにLinqクエリの一部になるオブジェクトの作成が実際に必要です。


更新:新しいEFコア2.0でテストしたところ、問題は残っています。 (21-08-2017)

問題がaspnet/EntityFrameworkCore GitHubのレポで追跡されています。Issue 4007

更新:年以降では、この問題は、以下の私の答えを参照してください、バージョン2.1.0-preview1-finalで修正されています。 (01-03-2018)

+0

レイジーローディングをどこで/どのようにオフにしていますか? –

+0

遅延ロードはEF6に適用されます。私の質問はEFコアについてです。私が知る限り、コアには遅延ロードがありません。 – GWigWam

+1

Ah、okidoke ...あなたが実行しようとしているインクルードは、クエリーが始まるタイプのエンティティインスタンスを返さないので無視されています。 –

答えて

1

GitHubの問題#4007は、マイルストーン2.1.0-preview1closed-fixedとマークされています。そして今、2.136プレビュー1は.NET Blog postで説明したようにNuGetで利用可能になりました。

まず、新しいバージョンをインストールします。

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.0-preview1-final 

その後、結果をすぐにフェッチする必要があることを示すために、ネストされた.Select(x => ...).ToList()を使用しています。私の元の質問の場合、これは次のようになります。

var query = context.Top 
    .Select(t => new { 
     prop1 = t.C1, 
     prop2 = t.Sub.Select(s => new { 
      prop21 = s.C3 
     }) 
     .ToList() // <-- Add this 
    }); 
var res = query.ToArray(); // Execute the Linq query 

この結果、2つのSQLクエリがN + 1の代わりにデータベースで実行されます。最初に平文SELECTFROMの 'トップ'テーブル、次にSELECTFROMINNER JOINFROMのキー - 外部キーの関係[Sub].[TopId] = [Top].[Id]に基づいてINNER JOINFROMのテーブルを持つ 'サブ'テーブル。これらのクエリの結果は、メモリ内で結合されます。

結果は、あなたが期待する正確に何で、何EF6に非常によく似て戻ってきたでしょう:prop2プロパティprop21を持っている匿名型'bの一覧である性質prop1prop2を持っている匿名型'aの配列を。最も重要なのは.ToArray()コールの後にすべてが完全にロードされています!

1

私は同じ問題に直面しました。

提案したソリューションは、比較的大きなテーブルでは機能しません。生成されたクエリを見た場合、条件なしの内部結合になります。

するvar QUERY2 = context.Top .INCLUDE(トン=> t.Sub) .ToArray() .Select(トン=>新しい// ...必要なものを選択し、匿名の種類を記入してください

私はより良い解決策を聞いてうれしいですが、私はデータベースの再設計でそれを解決しました。

私の場合、テーブルAとテーブルBの2つがあります。テーブルAにはBと1対多があります。あなたが書いたようにリストで直接解決しようとしたとき、 .NET LINQの時間は0.5秒でしたが、.NET Core LINQは30秒の実行時間後に失敗しました)。

その結果、テーブルBの外部キーを作成し、内部リストなしでテーブルBの側から開始する必要がありました。

context.A.Where(a => a.B.ID == 1).ToArray(); 

結果の.NETオブジェクトを操作するだけで簡単に操作できます。

+0

場合によっては、これは十分な回避策になる可能性がありますが、私の場合はAutoMapperを使用するときや匿名オブジェクト。 'A' objを持つ' B'のリストを取得する 'A'のリストを持つ' B'を返すのではなく、結果セットを変更します。 ありがとうあなたの答えは、私は解決策としてこれを受け入れません。 – GWigWam

関連する問題