EF Core Nested Linq selectはN + 1個のSQLクエリで結果を返します

c# entity-framework-core linq select-n-plus-1 sql-server

質問

私は 'トップ'オブジェクトが0とN 'サブ'オブジェクトの間にあるデータモデルを持っています。 SQLでは、これは外部キーdbo.Sub.TopIdます。

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

Entity Framework 6(遅延ロードあり)では、このLinqクエリは単一の SQLクエリに変換されます。結果は完全にロードされるので、 res[0].prop2は既に埋め込まれているIEnumerable<SomeAnonymousType>になります。

ただし、EntityFrameworkCore(NuGet v1.1.0)を使用する場合、サブコレクションはまだロードされておらず、タイプは次のとおりです。

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

データを反復処理してN + 1回のクエリを実行するまで、データはロードされません。 .ToArray()を(コメントのように)クエリに追加すると、データはSQLプロファイラを使用してvar res完全にロードされますが、これは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 Core 2.0でテストしたところ、問題は残っています。 (21-08-2017)

問題はaspnet/EntityFrameworkCore GitHubレポで追跡されます: 問題4007

アップデート: 1年後、この問題はバージョン2.1.0-preview1-final修正されました。 (2018-03-01)

アップデート: EFバージョン2.1がリリースされました。これには修正が含まれています。下の私の答えを参照してください。 (2018-05-31)

受け入れられた回答

GitHub issue #4007はマイルストーン2.1.0-preview1 closed-fixedとしてマークされています。そして今、この.NETブログ記事で議論されているように、2.1プレビュー1がNuGetで利用可能になりました。

バージョン2.1本体もリリースされていますので、次のコマンドでインストールしてください。

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.0

次に、ネストされた.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

これにより、データベースで(N + 1ではなく)2つのSQLクエリが実行されます。まず普通はSELECT FROM 「上部」テーブル、次にSELECT FROMと「サブ」テーブルINNER JOIN FROMキーForeignKeyの関係に基づいて、「上部」テーブル[Sub].[TopId] = [Top].[Id] 。これらのクエリの結果はメモリ内で結合されます。

匿名型の配列を:結果は、あなたが期待する正確に何で、何EF6に非常によく似て戻っているだろう'aプロパティ有しているprop1prop2 prop2匿名型のリストである'bプロパティがあるprop21 。最も重要なことは、これらすべてが.ToArray()呼び出しの後に完全にロードされたことです。


人気のある回答

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

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

var query2 = context.Top .Include(t => t.Sub).ToArray().Select(t => new // ...必要なものを選択し、匿名型を記入します。

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

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

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

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

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



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ