实体框架 + LINQ + “包含” == 超慢
本文关键字:超慢 包含 框架 LINQ 实体 | 更新日期: 2023-09-27 18:30:37
试图重构一些最近变得非常慢的代码,我遇到了一个需要 5+ 秒才能执行的代码块。
该代码由 2 个语句组成:
IEnumerable<int> StudentIds = _entities.Filters
.Where(x => x.TeacherId == Profile.TeacherId.Value && x.StudentId != null)
.Select(x => x.StudentId)
.Distinct<int>();
和
_entities.StudentClassrooms
.Include("ClassroomTerm.Classroom.School.District")
.Include("ClassroomTerm.Teacher.Profile")
.Include("Student")
.Where(x => StudentIds.Contains(x.StudentId)
&& x.ClassroomTerm.IsActive
&& x.ClassroomTerm.Classroom.IsActive
&& x.ClassroomTerm.Classroom.School.IsActive
&& x.ClassroomTerm.Classroom.School.District.IsActive).AsQueryable<StudentClassroom>();
所以这有点混乱,但首先我从一个表(过滤器)中获取一个 Id 的不同列表,然后我使用它查询另一个表。
这些是相对较小的表,但仍然是 5+ 秒的查询时间。
我把它放在 LINQPad 中,它表明它首先执行底部查询,然后运行 1000 个"不同"查询。
一时兴起,我通过添加 .ToArray() 在最后。 这提高了 1000 倍的速度......现在大约需要 100 毫秒才能完成相同的查询。
怎么回事? 我做错了什么?
Linq 中延迟执行的缺陷之一: 在您的第一种方法中,StudentIds
实际上是一个IQueryable
,而不是内存中的集合。这意味着在第二个查询中使用它将在数据库上再次运行查询 - 每次。
使用 ToArray()
强制执行第一个查询会使StudentIds
成为内存中的集合,第二个查询中的Contains
部分将在此包含固定项目序列的集合上运行 - 这将映射到等效于 SQL where StudentId in (1,2,3,4)
查询的内容。
查询的速度要快得多,因为您预先确定了此序列一次,而不是每次执行Where
子句时。不使用ToArray()
的第二个查询(我认为)将映射到具有where exists (...)
子查询的 SQL 查询,该子查询针对每一行进行评估。
ToArray()
具体化对服务器内存的初始查询。
我的猜测是查询提供程序无法解析表达式StudentIds.Contains(x.StudentId)
。因此,它可能认为studentIds
是一个已经加载到内存中的数组。因此,它可能在解析阶段一遍又一遍地查询数据库。确定的唯一方法是设置探查器。
如果需要在数据库服务器上执行此操作,请使用连接,而不是"包含"。如果需要使用 包含 来执行看起来像联接问题的操作,则很可能缺少代理项主键或某处的外键。
您也可以将studentIds
声明为 IQueryable 而不是 IEnumerable。这可能会为查询提供程序提供将studentIds
解释为表达式(又名表达式)所需的提示。数据尚未加载到内存。我不知何故对此表示怀疑,但值得一试。
如果所有其他方法都失败,请使用 ToArray()
。这会将初始studentIds
加载到内存中。