17

试图重构一些最近变得非常慢的代码,我遇到了一个需要 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() 更改了“StudentIds”代码。这将速度提高了 1000 倍……现在完成相同的查询需要 100 毫秒。

这是怎么回事?我究竟做错了什么?

4

2 回答 2

25

这是 Linq 中延迟执行的缺陷之一:在您的第一种方法StudentIds中,实际上是一个IQueryable,而不是内存中的集合。这意味着在第二个查询中使用它会在数据库上再次运行查询 - 每次。

通过 using 强制执行第一个查询ToArray()StudentIds生成一个内存集合,并且Contains第二个查询中的部分将在包含固定项目序列的该集合上运行 - 这将映射到与 SQLwhere StudentId in (1,2,3,4)查询等效的内容。

这个查询当然会快得多,因为您预先确定了这个序列,而不是每次Where执行子句时。您不使用ToArray()(我认为)的第二个查询将映射到一个 SQL 查询,其中包含一个where exists (...)针对每一行进行评估的子查询。

于 2012-04-09T22:38:07.397 回答
4

ToArray()将初始查询具体化到服务器内存。

我的猜测是查询提供程序无法解析表达式StudentIds.Contains(x.StudentId)。因此它可能认为studentIds是一个已经加载到内存中的数组。所以它可能在解析阶段一遍又一遍地查询数据库。确定的唯一方法是设置分析器。

如果您需要在数据库服务器上执行此操作,请使用连接,而不是“包含”。如果您需要使用 contains 来执行看起来像连接问题的操作,您可能会在某处丢失代理主键或外键。

您也可以声明studentIds为 IQueryable 而不是 IEnumerable。这可能会为查询提供者提供解释studentIdsas 表达式所需的提示。数据尚未加载到内存中。我以某种方式怀疑这一点,但值得一试。

如果一切都失败了,请使用ToArray(). 这会将初始值加载studentIds到内存中。

于 2012-04-09T22:44:08.273 回答