C# 为什么EF在执行一对多连接时不必要地检索ID?
假设您有一个C# 为什么EF在执行一对多连接时不必要地检索ID?,c#,sql-server,entity-framework,entity-framework-core,C#,Sql Server,Entity Framework,Entity Framework Core,假设您有一个作者域类和一个书籍域类,每个作者可以有0个或多个书籍(一对多关系) 如果您执行以下操作: var dtos = dbContext.Authors.Select(a => new { Name = a.Name, BookNames = a.Books.Select(b => b.Name).ToList() }).ToList(); 您希望生成以下SQL,因为您只请求了作者的姓名以及每个作者的书籍名称: SELECT [a].[Name], [b].
作者
域类和一个书籍
域类,每个作者
可以有0个或多个书籍
(一对多关系)
如果您执行以下操作:
var dtos = dbContext.Authors.Select(a => new
{
Name = a.Name,
BookNames = a.Books.Select(b => b.Name).ToList()
}).ToList();
您希望生成以下SQL,因为您只请求了作者的姓名以及每个作者的书籍名称:
SELECT [a].[Name], [b].[Name]
FROM [Authors] AS [a]
LEFT JOIN [Books] AS [b] ON [a].[Id] = [b].[AuthorId]
ORDER BY [a].[Id], [b].[Id]
但是,实体框架会生成以下SQL:
SELECT [a].[Name], [a].[Id], [b].[Name], [b].[Id]
FROM [Authors] AS [a]
LEFT JOIN [Books] AS [b] ON [a].[Id] = [b].[AuthorId]
ORDER BY [a].[Id], [b].[Id]
这显然是不必要的检索所有作者的ID和他们所有书籍的ID
这是当你从另一边,也就是从书到作者,加入时,这不会发生。例如,当您请求书名及其作者姓名时,如:
var dtos = dbContext.Books.Select(b => new
{
Name = b.Name,
BookNames = b.Author == null ? null : b.Author.Name
}).ToList();
为上述代码生成的SQL将是:
SELECT [b].[Name], CASE
WHEN [a].[Id] IS NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, [a].[Name]
FROM [Books] AS [b]
LEFT JOIN [Authors] AS [a] ON [b].[AuthorId] = [a].[Id]
未按预期检索ID
为什么会这样?为什么EF Core会在我的投影中没有包含ID的情况下检索ID
我已经用EF Core 3.x+5.0 RC1对其进行了测试。如果这很重要。SQL查询结果集是平面的。如果您的查询也是平面的,那么您期望的查询也会被生成(实际上是在您从另一方进行查询时生成的),如下所示
var query =
from a in dbContext.Authors
from b in a.Books.DefaultIfEmpty()
select new { Name = a.Name, BookName = b.Name };
但是,您的查询结果形状不同-名称+相关图书名称列表。通常SQL不能提供这样的形状,因此EF Core应该以某种方式转换(组)返回的平面结果集客户端。记录按两个ID排序,但正确分组也需要这两个ID。从技术上讲,除了顺序中的最后一个Id之外,所有Id都是多余的,因此在本例中,[b].[Id]
。因此,他们可以按顺序将它们分组(无需在内存中缓冲整个结果集,如常规LINQ to ObjectsGroupBy
),如下所示(在伪代码中):
var enumerator=query.GetEnumerator();
bool hasNext=enumerator.MoveNext();
while(hasNext)
{
var aId=枚举数。当前[“aId”]/Ivan Stoev解释了第一个查询返回ID的原因。第二个查询非常不同。第一个查询为每个作者返回一个根对象,其中包含所有作者的书籍,而第二个查询返回一个简单的对象列表。在这种情况下,不需要知道ID。从作者开始的等效项是
var bookNames= from a in dbContext.Authors
from b in a.Books
select new {a.Name,BookNames=new {b.Name}};
猜一猜,反对/改变tracking@Diado没有要跟踪的对象,因为未选择实体,而是一个只包含名称的匿名对象。如果您仔细想想,您不是在要求平面作者、书名对。您是在要求多本书的一个作者名称。如果没有PK v,EF无法生成该层次结构值。没有什么可以阻止多个作者拥有相同的名称,您如何知道哪本书属于哪位作者?第二个查询非常不同,不需要ID。每本书只有一个可能的AuthorName。即使如此,仍会检查author.ID
,以确保joinOk生成任何可能的NULL
s,明白了。谢谢。所以使用[a].[Id]
是合理的。但是正如你自己提到的,[b].[Id]
是多余的,那么为什么EF Core在这种情况下检索它呢?@Arad Bug/defect/missing optimization。我们都会做这样的事情,尤其是在有截止日期和不同优先级的情况下。