【问题标题】:Entity Framework: Left Join with List Result实体框架:左连接与列表结果
【发布时间】:2019-06-15 01:18:19
【问题描述】:

我正在尝试优化我的 EF 查询。我有一个名为 Employee 的实体。每个员工都有一份工具清单。最终,我正在尝试使用未损坏的工具获取员工列表。运行查询时,我可以看到对服务器进行了两次调用:一次用于员工实体,一次用于工具列表。同样,我正在尝试优化查询,因此服务器只被命中一次查询。我该怎么做?

我一直在探索 LINQ 的 join 以及如何创建 LEFT JOIN,但查询仍未优化。

在我的第一个代码块中,结果是我想要的,但是 - 再次 - 服务器有两次命中。

public class Employee
{
    public int EmployeeId { get; set; }
    public List<Tool> Tools { get; set; } = new List<Tool>();
    ...
}

public class Tool
{
    public int ToolId { get; set; }
    public bool IsBroken { get; set; } = false;

    public Employee Employee { get; set; }
    public int EmployeeId { get; set; }
    ...
}
var x = (from e in db.Employees.Include(e => e.Tools)
         select new Employee()
         {
             EmployeeId = e.EmployeeId,
             Tools = e.Tools.Where(t => !t.IsBroken).ToList()
         }).ToList();

第二个代码块模拟了我想要完成的工作。但是,GroupBy(...) 正在客户端计算机上进行本地评估。

(from e in db.Employees
 join t in db.Tools.GroupBy(tool => tool.EmployeeId) on e.EmployeeId equals t.Key into empTool
 from et in empTool.DefaultIfEmpty()
 select new Employee()
 {
    EmployeeId = e.EmployeeId,
    Tools = et != null ? et.Where(t => !t.IsBroken).ToList() : null
 }).ToList();

无论如何,我是否可以对服务器进行一次调用,而不是让我的 GroupBy() 在本地进行评估,并让它返回一个员工列表,其中包含未损坏的工具列表的过滤工具列表?谢谢。

【问题讨论】:

  • 调用服务器是指查询数据库,对吧?

标签: entity-framework linq entity-framework-core


【解决方案1】:

简而言之,这是不可能的(而且我认为永远不可能)。

如果您真的想控制确切的服务器调用,EF Core 根本不适合您。虽然 EF Core 仍然存在一些导致 N+1 查询或客户端评估的 LINQ 查询转换问题,但有一点是设计使然:与 EF6 不同,EF6 使用单个巨大的联合 SQL 查询来生成结果,EF Core 使用一个 SQL 查询主要结果集一个 SQL 查询每个相关结果集。

How Queries Work EF Core 文档部分对此进行了解释:

  1. LINQ 查询由 Entity Framework Core 处理以构建可供数据库提供程序处理的表示
    • 结果被缓存,所以每次执行查询时都不需要做这个处理
  2. 结果被传递给数据库提供者
    • 数据库提供程序确定可以在数据库中评估查询的哪些部分
    • 查询的这些部分被翻译成数据库特定的查询语言(例如,用于关系数据库的 SQL)
    • 一个或多个查询被发送到数据库并返回结果集(结果是来自数据库的值,而不是实体实例)

注意最后一个项目符号中的more这个词。

在您的情况下,您有 1 个主要结果集 (Employee) + 1 个相关结果集 (Tool),因此预期的服务器查询是两个(除非第一个查询返回空集)。

【讨论】:

    【解决方案2】:

    你可以用这个:

        var x = from e in _context.Employees
        select new
        {
            e,
            Tools = from tool in e.Tools where !tool.IsBroken select tool
        };
        var result = x.AsEnumerable().Select(y => y.e);
    

    最终将根据您的提供程序转换为如下 SQL 查询:

        SELECT
    `Project1`.`EmployeeId`, 
    `Project1`.`Name`, 
    `Project1`.`C1`, 
    `Project1`.`ToolId`, 
    `Project1`.`IsBroken`, 
    `Project1`.`EmployeeId1`
    FROM (SELECT
    `Extent1`.`EmployeeId`, 
    `Extent1`.`Name`, 
    `Extent2`.`ToolId`, 
    `Extent2`.`IsBroken`, 
    `Extent2`.`EmployeeId` AS `EmployeeId1`, 
    CASE WHEN (`Extent2`.`ToolId` IS NOT NULL) THEN (1)  ELSE (NULL) END AS `C1`
    FROM `Employees` AS `Extent1` LEFT OUTER JOIN `Tools` AS `Extent2` ON (`Extent1`.`EmployeeId` = `Extent2`.`EmployeeId`) AND (`Extent2`.`IsBroken` != 1)) AS `Project1`
     ORDER BY 
    `Project1`.`EmployeeId` ASC, 
    `Project1`.`C1` ASC
    

    感谢 cmets,我更改了之前错误的答案。

    【讨论】:

    • 其实恰恰相反 - ToList() inside 查询应该选择加入correlated subquery optimization
    • @IvanStoev 你是对的,我不知道这一点。我改变了答案。
    • 不幸的是,您似乎在 EF6 下进行测试,而问题是针对 EF Core,这是完全不同的系统(请参阅我的回答)。
    猜你喜欢
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多