我正在尝试使用最新的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();
答案 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只能做很多事情来优化查询,而无论您做什么,这都是效率极低的查询。