「トップ」オブジェクトに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クエリはsingleSQLクエリに変換されます。結果は完全に読み込まれるので、_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クエリではこれが実現されないことが示されます。 「トップ」オブジェクトごとに、「サブ」テーブルに対するクエリが実行されます。
最初に.Include(t => t.Sub)
を指定しても、何も変更されないようです。匿名型の使用も問題ではないようです。_new { ... }
_ブロックを_new MyPocoClass { ... }
_で置き換えても何も変わりません。
私の質問は:すべてのデータがすぐに読み込まれるEF6に似た動作を取得する方法はありますか?
注:この例では、メモリに匿名オブジェクトをafterのようにクエリを実行することで問題を解決できることを理解しています:
_var query2 = context.Top
.Include(t => t.Sub)
.ToArray()
.Select(t => new //... select what is needed, fill anonymous types
_
ただし、これは単なる例であり、実際にはオブジェクトの作成がLinqクエリの一部である必要があります。AutoMapperがこれを使用してプロジェクトのDTOを埋める
更新:新しいEF Core 2.0でテストされ、問題はまだ発生しています。 (2017年8月21日)
問題は_aspnet/EntityFrameworkCore
_ GitHubリポジトリで追跡されます: Issue 4007
更新:1年後、この問題はバージョン_2.1.0-preview1-final
_で修正されました。 (2018-03-01)
更新:EFバージョン2.1がリリースされました。修正が含まれています。以下の私の答えを見てください。 (2018-05-31)
GitHubの問題 #4007 がマイルストーン_closed-fixed
_の_2.1.0-preview1
_としてマークされました。そして、この2.1-preview1は NuGet で利用可能になりました 。NETブログの投稿 で説明されています。
適切なバージョン2.1もリリースされています。次のコマンドを使用してインストールします。
_Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.0
_
次に、ネストされた.ToList()
で.Select(x => ...)
を使用して、結果をすぐにフェッチする必要があることを示します。私の元の質問では、これは次のようになります:
_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
Key-ForeignKey関係_[Sub].[TopId] = [Top].[Id]
_に基づく「トップ」テーブル。これらのクエリの結果は、メモリ内で結合されます。
結果は期待どおりであり、EF6が返すものと非常に似ています。_'a
_および_prop1
_のプロパティを持つ匿名型_prop2
_の配列_prop2
_はaプロパティ_'b
_を持つ匿名型_prop21
_のリスト。最も重要なのは.ToArray()
の呼び出し後にすべてが完全に読み込まれることです
私も同じ問題に直面しました。
あなたが提案したソリューションは、比較的大きなテーブルでは機能しません。生成されたクエリを確認すると、where条件のない内部結合になります。
var query2 = context.Top .Include(t => t.Sub).ToArray().Select(t => new // ...必要なものを選択し、匿名型を入力します
データベースの再設計で解決しましたが、もっと良い解決策があったら嬉しいです。
私の場合、私は2つのテーブルAとBを持っています。テーブルAはBと1対多です。あなたが述べたようにリストで直接解決しようとしたとき、どうにかできませんでした(.NETの実行時間) LINQは0.5秒でしたが、.NET Core LINQは実行時間30秒後に失敗しました)。
その結果、テーブルBの外部キーを作成し、内部リストなしでテーブルBのサイドから開始する必要がありました。
context.A.Where(a => a.B.ID == 1).ToArray();
その後、結果の.NETオブジェクトを簡単に操作できます。