【问题标题】:Linq query runs in a fraction of a second, but .ToList() takes 3.5 secondsLinq 查询在几分之一秒内运行,但 .ToList() 需要 3.5 秒
【发布时间】:2013-05-24 14:32:14
【问题描述】:

我正在运行一个 Linq 查询,它返回大约 25 条记录,每条记录有 10 个数字列。根据我的代码分析器,查询本身只需要几分之一秒 - 但对 .ToList() 的调用大约需要 3.5 秒。如前所述,从 SQL 返回的数据量是微不足道的,因此将其复制到 List 所花费的时间不应该是繁重的。

为什么.ToList() 需要这么长时间?又该如何改进?

编辑:感谢所有快速的答案,让我更清楚地说明:我完全知道查询是延迟加载的事实。我看到的现象是 SQL Server Profiler 和 ANTS Performance Profiler 都报告实际查询执行时间只有几分之一秒。

这是 ANTS 的屏幕截图:

请注意,调用方法耗时 4.3 秒,而实际 SQL 查询的耗时均不超过 0.05 秒。它可能是该方法中的其他代码,而不是 SQL?让我们看看 ANTS 是如何在这里分解代码配置文件的:

确凿证据:.ToList() 用时 3.36 秒,其中可能是 0.05 秒可归因于实际查询执行时间,剩下的 3.31 秒下落不明。

时间要去哪里?

编辑 2: 好的,你要求它,所以这是我的代码:

public static Expression<Func<Student, Chart>> GetStudentAssessmentQuestionResultByStudentIdNew(MyDataEntities db)
{
    return s => new Chart
        {
            studentID = s.ID,
            Lines =
                    db.StudentAssessmentAnswers
                        .Where(
                                saa =>
                                saa.StudentAssessment.BorrowedBook.StudentID == s.ID && saa.PointsAwarded != null &&
                                saa.Question.PointValue > 0 &&
                                (saa.Question.QuestionType == QuestionType.MultipleChoice ||
                                 saa.Question.QuestionType == QuestionType.OpenEnded))
                        .GroupBy(
                                saa =>
                                new
                                {
                                    saa.StudentAssessment.AssessmentYear,
                                    saa.StudentAssessment.AssessmentMonth,
                                    saa.Question.CommonCoreStandard
                                },
                                saa => saa)
                        .Select(x => new
                                     {
                                         x.Key.AssessmentYear,
                                         x.Key.AssessmentMonth,
                                         x.Key.CommonCoreStandard,
                                         PercentagePointValue =
                                         (float)(x.Sum(a => a.PointsAwarded) * 100) / (x.Sum(a => a.Question.PointValue))
                                     })
                        .OrderByDescending(x => x.CommonCoreStandard)
                        .GroupBy(r1 => (byte)r1.CommonCoreStandard)
                        .Select(g => new ChartLine
                                     {
                                         ChartType = ((ChartType)g.Key),
                                         //type = g.Key.ToString(),
                                         type = g.Key,
                                         Points = g.Select(grp => new ChartPoint
                                                {
                                                    Year = grp.AssessmentYear.Value,
                                                    Month = grp.AssessmentMonth.Value,
                                                    yValue = grp.PercentagePointValue
                                                })
                                     })
        };
}

这是由以下人员调用的:

var students =
    db.ClassEnrollments
        .Where(ce => ce.SchoolClass.HomeRoomTeacherID == teacherID)
        .Select(s => s.Student);
var charts = CCProgressChart.GetStudentAssessmentQuestionResultByStudentIdNew(db);
var chartList = students.Select(charts).ToList();

这有帮助吗?

【问题讨论】:

  • 也许你有这样的db.Select(r =&gt; somefields).ToList().Where(i =&gt; filterHere);。然后您的查询将选择所有记录,您将在内存中过滤它们。将Where 放在具体化之前,所以放在ToList 之前。
  • 您的查询什么?
  • @Tim 提到的一个常见的微妙之处是,如果您过早将类型更改为 IEnumerable&lt;T&gt;,而不是 IQueryable&lt;T&gt; - 例如:IEnumerable&lt;Person&gt; people = db.People; var qry = people.Where(x =&gt; x.Id == 12345).ToList(); - 这会将整个表格拖到网络并在 LINQ-to-Objects 中执行Where,而不是通过 EF(它将在数据库中进行过滤)。所以:你能显示一些代码吗?
  • @MarcGravell - 请查看我的编辑
  • @TimSchmelter - 请查看我的编辑

标签: c# linq linq-to-entities entity-framework-5


【解决方案1】:

LINQ to SQL 是延迟加载的。在调用 ToList() 之前实际上什么都不会发生。

编辑:

由于您已更新答案,因此需要注意一些事项。 ToList() 在构造函数中占用了 73.4% 的时间。这是实际执行 SQL 语句的地方。下面是实际的 ToList 方法:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source != null)
    {
        return new List<TSource>(source);
    }
    else
    {
        throw Error.ArgumentNull("source");
    }
}

在调用以下行之前,您的 SQL 不会真正执行:

return new List<TSource>(source);

所以是的 ToList 是永远的,但那是因为它开始处理源参数。 Antz 是一个非常有用的工具,但它有时会导致您走错路。问题很可能是其中之一:

  1. 生成的 T-SQL 可怕且缓慢。
  2. 打开与 SQL 服务器的连接很慢
  3. 您的 lambda 表达式树很复杂,实体框架需要很长时间来处理它并生成 T-SQL

我会首先打开 SQL 分析器并查看 SQL。即使看起来最简单的语句也可能做一些疯狂的事情,唯一确定的方法是查看实际的 T-SQL。

如果 T-SQL 没问题,您将不得不使用 Lambda 表达式以使其更简单、更易于处理。

【讨论】:

  • 谢谢。事实证明,ANTS 对我做了海森堡……见my answer
【解决方案2】:

好吧,毕竟,事实证明 ANTS 是在对我们做海森堡。

事情的结果是,我最初收到了关于这方面表现不佳的投诉。我运行了 ANTS 并确定该代码是负责任的。所以我重构,优化,结果就是你在问题中看到的代码。然后我重新测试,发现有很大的改进,但性能仍然无法接受。然后是 SO 问题。

然后我决定尝试在没有 ANTS 的情况下运行单元测试......它在几分之一秒内运行。

经验教训:有时性能分析器本身就是性能不佳的原因。

【讨论】:

    【解决方案3】:

    没有更多细节,很难确定。但是,你的List 是什么类型的?该类在构造函数/设置器中做了什么?它是实现 INotifyPropertyChanged,还是触发任何其他事件?

    请记住,只有在您调用 ToList 时才会执行查询。在那之前,它只是一个可查询的对象。使用 SQL 分析器查看它,您会看到它在进行数据库访问时就是

    【讨论】:

      【解决方案4】:

      Linq2SQL 使用称为延迟加载的东西。基本上,在您查看您要求的容器之前,它不会靠近数据源。你可以继续建立你的查询,但是一旦你列出它,或者查看第一个,它就会建立你的查询,将它发送到数据库并等待结果......因此需要很长时间

      【讨论】:

        【解决方案5】:

        .ToList() 实际上是执行查询。因此,您的查询需要 2.5 秒才能运行。

        阅读更多关于延迟执行的信息here

        如果不发布您的实际 LINQ 查询,我们将无法帮助您提高性能(如果您发布它,我会更新我的答案)。

        【讨论】:

        • @Shaul 你能发布有问题的 LINQ 吗?看起来您可能会返回 很多 行(就像有人说的那样,执行 .ToList()然后 执行 .Where())。
        • 这有关系吗?如果分析器告诉我查询在 0.05 秒内执行,那么查询不需要优化?
        • @Shaul 老实说,我真的不相信 ANTS 中的时间安排是 完全 如所述。没有看到您的任何代码,没有人可以进一步帮助您。
        • @Shaul 出于兴趣,如果您在.Select(s =&gt; s.Student) 之后调用.ToList(),这会影响时间吗?
        • @Shaul 我跟你说了什么?我不相信 ANTS 计时:P
        猜你喜欢
        • 2018-08-16
        • 1970-01-01
        • 1970-01-01
        • 2012-12-16
        • 2010-10-11
        • 2014-12-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多