为什么EF核心对n + 1个查询执行n个查询

时间:2019-09-04 10:01:23

标签: asp.net-core-2.2 ef-core-2.2

我正在尝试使用最新的DepartmentLog获取Departments,但是ef内核执行n + 1查询。而且工作如此缓慢。我在Department表上有10000行,在DepartmentLogs上有12000行。我不确定执行此查询必须工作1.5s-2s。

 var query2 = _unitOfWork.Departments.GetDbSet()
            .Include(p => p.CreatedUser)
            .ThenInclude(p => p.Employee)
            .OrderByDescending(p => p.CreatedAt)
            .Select(p => new
            {
                Department = p,
                DepartmentLog = _unitOfWork.DepartmentLogs.GetDbSet()
                .Include(m => m.CreatedUser)
                .ThenInclude(m => m.Employee)
                .OrderByDescending(m => m.CreatedAt)
                .FirstOrDefault(m => m.DepartmentId == p.Id)
            })
        .Take(10).ToList();

1 个答案:

答案 0 :(得分:0)

您要传递给Select的lambda是针对结果集中的每个项目执行的。由于您要在该lambda中进行查询,因此您将发出N条查询,结果集中的每个项目都会发出一个查询。

当您删除FirstOrDefault时,它仅生成一个额外查询的原因是,您将返回所有部门日志,然后允许EF将它们添加到对象缓存中,以便在其中将它们拉出从以后开始,而不是发出新查询。使用FirstOrDefault,您只返回一个,因此EF缓存那个,但是在下一次运行lambda时,您选择的是另一个未缓存的。

要使用FirstOrDefault并且仍然只有一个额外的查询,应首先 运行该查询:

var departmentLogs = await _unitOfWork.DepartmentLogs.GetDbSet()
    .Include(m => m.CreatedUser)
    .ThenInclude(m => m.Employee)
    .OrderByDescending(m => m.CreatedAt)
    .ToListAsync();

然后,在您的Select中:

.Select(p => new
{
    Department = p,
    DepartmentLog = departmentLogs.FirstOrDefault(m => m.DepartmentId == p.Id)
})

然后,它将对内存中已经存在的结果集运行FirstOrDefault。但是,这需要返回 all 日志,如果存在大量日志,则在性能方面可能会出现更多问题。您将必须通过各种方式进行分析,并查看在特定情况下哪种效果更好。

或者,一个可能最好的选择是,您可以简单地将此查询创建为存储过程,然后仅调用存储过程来获取结果,而不是使用LINQ来构建查询。 EF只能做很多事情来优化查询,而无论您做什么,这都是效率极低的查询。