实体框架+ LINQ +“包含”==超慢?

时间:2012-04-09 22:34:18

标签: c# linq entity-framework

尝试重构一些最近变得非常慢的代码,然后我遇到了一个执行时间超过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才能完成相同的查询。

这笔交易是什么?我做错了什么?

2 个答案:

答案 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加载到内存中。