尝试重构一些最近变得非常慢的代码,然后我遇到了一个执行时间超过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&#34; distinct&#34;之后的询问。
我一时兴起改变了#34; StudentIds&#34;代码只需在最后添加.ToArray()即可。这提高了速度1000x ...现在需要100ms才能完成相同的查询。
这笔交易是什么?我做错了什么?
答案 0 :(得分:24)
这是Linq中延迟执行的陷阱之一:在您的第一个方法中StudentIds
实际上是IQueryable
,而不是内存中的集合。这意味着在第二个查询中使用它将在数据库上再次运行查询 - 每次都是。
使用ToArray()
强制执行第一个查询会使StudentIds
成为内存中的集合,而第二个查询中的Contains
部分将在此集合上运行,该集合包含一个固定的序列items - 这被映射到等同于SQL where StudentId in (1,2,3,4)
查询的东西。
这个查询当然要快得多,因为你预先确定了这个序列,而不是每次都执行Where
子句。不使用ToArray()
的第二个查询(我认为)将被映射到带有where exists (...)
子查询的SQL查询,该查询将针对每一行进行评估。
答案 1 :(得分:4)
ToArray()
将初始查询具体化到服务器内存。
我的猜测是查询提供程序无法解析表达式StudentIds.Contains(x.StudentId)
。因此,它可能认为studentIds
是已经加载到内存的数组。所以它可能在解析阶段一遍又一遍地查询数据库。确切知道的唯一方法是设置探查器。
如果需要在数据库服务器上执行此操作,请使用连接,而不是“包含”。如果您需要使用contains来执行看起来像连接问题的操作,那么您可能会在某处丢失代理主键或外键。
您也可以将studentIds
声明为IQueryable而不是IEnumerable。这可能会为查询提供程序提供将studentIds
解释为表达式所需的提示。数据尚未加载到内存中。我不知何故怀疑这一点,但值得一试。
如果其他所有方法都失败了,请使用ToArray()
。这会将初始studentIds
加载到内存中。