我有一个数据模型,其中'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()
时(如注释中所示),使用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
然而,这只是一个例子,我确实需要创建对象作为Linq查询的一部分,因为AutoMapper使用它来填充我的项目中的DTO
更新:使用新的EF Core 2.0测试,问题仍然存在。 (21-08-2017)
跟踪问题 aspnet/EntityFrameworkCore
GitHub回购:Issue 4007
更新:一年后,此问题已在版本 2.1.0-preview1-final
中得到修复。 (2018年3月1日)
更新: EF版本2.1已发布,它包含一个修复程序。请参阅下面的答案。 (2018年5月31日)
答案 0 :(得分:7)
对于里程碑closed-fixed
,GitHub问题#4007已被标记为2.1.0-preview1
。现在,NuGet已经在.NET Blog post上提供了2.1预览1。
版本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
'热门'表格,然后是SELECT
FROM
'子'表,其中INNER JOIN
FROM
为' Top'表,基于Key-ForeignKey关系[Sub].[TopId] = [Top].[Id]
。然后将这些查询的结果合并到内存中。
结果正如您所期望的那样,与EF6将返回的内容非常相似:匿名类型'a
的数组,其属性prop1
和prop2
其中{{1} }是具有属性prop2
的匿名类型'b
的列表。最重要的是所有这些都是prop21
来电后完全加载的!
答案 1 :(得分:1)
我遇到了同样的问题。
您提出的解决方案对于相对较大的表不起作用。如果您查看生成的查询,那么它将是一个没有where条件的内连接。
var query2 = context.Top .Include(t =&gt; t.Sub) .ToArray() 。选择(t =&gt;新// ...选择需要的内容,填写匿名类型
我通过重新设计数据库解决了这个问题,但我很乐意听到更好的解决方案。
在我的情况下,我有两个表A和B.表A与B有一对多。当我试图用你所描述的列表直接解决它时我没有设法做到(运行时间) for .NET LINQ是0.5秒,而.NET Core LINQ在运行30秒后失败了。)
因此,我必须为表B创建一个外键,并从表B的一侧开始,没有内部列表。
context.A.Where(a => a.B.ID == 1).ToArray();
之后,您可以简单地操作生成的.NET对象。