我有一个数据模型,其中'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
填充.
使用EntityFrameworkCore(NuGet v1.1.0)时,子集合尚未加载且类型为:
System.Linq.Enumerable.WhereSelectEnumerableIteratorf__AnonymousType1 >.
在迭代数据之前,不会加载数据,从而导致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
然而,这只是一个例子,我确实需要创建对象作为Linq查询的一部分,因为AutoMapper使用它来填充我的项目中的DTO
更新:使用新的EF Core 2.0测试,问题仍然存在.(21-08-2017)
问题在aspnet/EntityFrameworkCore
GitHub回购中跟踪:问题4007
更新:一年后,此问题已在版本中修复2.1.0-preview1-final
.(2018年3月1日)
更新: EF版本2.1已发布,它包含一个修复程序.请参阅下面的答案.(2018年5月31日)
GitHub问题#4007已被标记closed-fixed
为里程碑2.1.0-preview1
.现在2.1预览1已经在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
这导致在数据库上运行2个SQL查询(而不是N + 1); 首先是一个简单SELECT
FROM
的'Top'表,然后是一个带有'Top'表SELECT
FROM
的'Sub INNER JOIN
FROM
'表,基于Key-ForeignKey关系[Sub].[TopId] = [Top].[Id]
.然后将这些查询的结果合并到内存中.
结果是什么EF6将返回正是你所期望的,非常相似:匿名类型的数组,'a
它具有性能prop1
和prop2
在那里prop2
是匿名类型的列表'b
它有一个属性prop21
.最重要的是,所有这些都是在.ToArray()
通话后满载的!