【发布时间】:2017-01-10 16:23:21
【问题描述】:
我有一个数据模型,其中“Top”对象有 0 到 N 个“Sub”对象。在 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() 添加到查询中(如cmets 中所示)时,数据被完全加载到var res 中,但是使用SQL 分析器显示这不再在1 个SQL 查询中实现。对于每个 'Top' 对象,都会执行对 'Sub' 表的查询。
首先指定.Include(t => t.Sub) 似乎并没有改变任何东西。使用匿名类型似乎也不是问题,用new MyPocoClass { ... } 替换new { ... } 块不会改变任何东西。
我的问题是:有没有办法获得类似于 EF6 的行为,即立即加载所有数据?
注意:我意识到在这个例子中,问题可以通过在内存中创建匿名对象来解决在执行查询之后:
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 进行测试,问题仍然存在。 (21-08-2017)
问题在aspnet/EntityFrameworkCore GitHub repo 上跟踪:Issue 4007
更新:一年后,此问题已在版本2.1.0-preview1-final 中得到修复。 (2018-03-01)
更新: EF 2.1 版已发布,其中包含一个修复程序。请参阅下面的答案。 (2018-05-31)
【问题讨论】:
-
啊,okidoke...您用于尝试执行急切加载的包含被忽略,因为您没有返回查询开头类型的实体实例。
-
嗯...这是我认为最好坚持使用 EF6.x 直到 EFC 更加成熟的原因之一。
-
哇,你说的完全正确!另一个当前的 EF Core 奇怪行为。如果您进行手动连接,它会执行单个查询。这会扼杀导航属性的全部意义。谁知道如果您添加另一个实体访问器/连接会发生什么。伙计,EF Core 目前是一个......不可靠的,如果可以的话,切换回 EF6,否则你就不走运了:(
-
这是我连续第三年尝试使用 EF Core。为什么为什么为什么我会一直回到它,并希望这样的事情现在能奏效。
-
@Simon_Weaver 确实已经有一段时间了,但似乎他们已经修复了 2.1 版
标签: c# sql-server linq entity-framework-core select-n-plus-1