【问题标题】:Optimizing a slow LINQ query优化慢速 LINQ 查询
【发布时间】:2015-11-03 11:30:41
【问题描述】:

我有一个 LINQ 查询,我在优化时遇到了问题,运行大约需要 5.5 秒。我正在使用一个名为 StaffingResourceData 的视图和一个名为 StaffingForecasts 的表。

每个 StaffingResource 都有一个 ResourceId、一个部门和一个类型。 StaffingForecast 具有 ResourceId、Project、Date(表示一周中的星期一)、Hours。一个 StaffingResource 可以有 0 个 StaffingForecast。

对于每个 StaffingResource,我需要他们未来 12 周的总预测小时数列表。这是我现在拥有的:

// Get list of dates
var dates = new List<DateTime>();
var start = Utilities.GetStartOfWeek(DateTime.Today);
for (var i = 0; i < 12; i++)
{
    dates.Add(start.AddDays(i * 7));
}
var end = dates[11];

// Get resources
var resources = (from r in context.StaffingResourceDatas
                 where r.EmployeeId != null
                     && !exclusionList.Contains(r.ResourceTitleId)
                 join f in context.StaffingForecasts.Where(x => x.Date >= start && x.Date <= end) on r.ResourceId equals f.ResourceId into g1
                 from f in g1.DefaultIfEmpty()
                 group new { f.Date, f.Hours } by r into g2
                 select new ChartResourceModel
                 {
                     ResourceId = g2.Key.ResourceId,
                     Division = g2.Key.ResourceDivision,
                     Type = g2.Key.ResourceType,
                     Dates = dates.Select(d => new ChartDateModel
                     {
                         Date = d,
                         Available = (g2.Where(f => f.Date == d).Any() ? g2.Where(f => f.Date == d).Sum(f => f.Hours) : 0) < 24
                     }).ToList()
                 })
               .ToList();

关于如何加快速度的任何想法?

【问题讨论】:

  • Code Review 会更好吗?
  • 好吧,如果没有真正回答你的问题,你可以用 Any 的重载替换这个 Where(f =&gt; f.Date == d).Any() Func: Any(f =&gt; f.Date == d)
  • 看看正在生成的 SQL,这至少会给你一个开始。
  • group new { f.Date, f.Hours } by r into g2 的部分看起来很可疑,我从未见过这样的东西,也不知道它是做什么的。
  • 复杂的报告查询应该在视图中完成,而不是在 LINQ 中。加入 LINQ 意味着模型中缺少关系。添加关系,删除连接,让 EF 生成正确的 SQL。还要删除内部的ToList() 调用,它们会导致为每个日期执行单独的查询。您的代码可能会产生 1000 个查询而不是 1 个。

标签: c# performance entity-framework linq


【解决方案1】:
  1. 避免使用Contains。它会严重降低性能。 See this post

  2. ToList() 是执行查询的命令。在你调用ToList() 方法之前,linq 查询不会启动,因为 linq 有一个称为延迟执行的功能。因此,如果您调用 ToList(),您将开始对 Databaseof 文件进行一些实际操作。

  3. 减少表的列会减少所需的带宽(从查询中删除不必要的列)
  4. 关闭更改跟踪和身份管理(例如,LINQ-to-SQL 中的 ObjectTrackingEnabled)

    using (YourDataContext dataContext = new YourDataContext())    
    {
        dataContext.ObjectTrackingEnabled = false;    
        //Your code
    }
    
  5. 使用 EF 的调整选项之一,例如 .AsNoTracking()The extensive description can be seen here.
  6. 使用预编译查询。它有时会减少预处理开销

【讨论】:

  • 我在性能和包含方面也遇到了类似的问题.....有一件事是要避免它,或者至少在包含本身之前使用所有可能的部分 if 以减少包含的频率被调用。
  • 我知道这是旧的,但是当你发布它时 #1 已经过时了。该问题已在 EF6 中得到解决。
【解决方案2】:
  • 避免使用 .ToList(),这总是在扩展或使用相关实体时创建 sql 查询
  • CompiledQueries 提前编译查询,避免一些开销(不多)
  • 调试您的查询(例如使用http://miniprofiler.com/

【讨论】:

    【解决方案3】:

    玩了一段时间后,我能够将加载时间从 5.5 秒缩短到 1.5 秒。这是我想出的:

    // Get resources
    var resources = (from r in
                        (from r in context.StaffingResourceDatas
                         where r.EmployeeId != null
                             && !exclusionList.Contains(r.ResourceTitleId)
                         join f in context.StaffingForecasts on r.ResourceId equals f.ResourceId
                         group f by r into g
                         select new
                         {
                             Resource = g.Key,
                             Forecasts = g.Where(f => f.Date >= start && f.Date <= end && f.StaffingPotentialProject == null).ToList()
                         }).ToList()
                     group r.Forecasts by r.Resource into g
                     select new ChartResourceModel
                     {
                         ResourceId = g.Key.ReportsToId,
                         Division = g.Key.ResourceDivision,
                         Type = g.Key.ResourceType,
                         Dates = dates.Select(d => new ChartDateModel
                         {
                             Date = d,
                             Available = (g.SelectMany(f => f.Where(x => x.Date == d)).Sum(x => x.Hours)) < 24
                         }).ToList()
                     }).ToList();
    

    似乎最好的方法是获取您需要的所有数据并调用 .ToList() 而不尝试做任何花哨的事情,然后对该数据执行任何额外的操作。

    【讨论】:

      【解决方案4】:

      首先,除非一切都完成,否则尽量避免使用“.ToList()”,因为当你触发“.ToList()”时,结果会被物化,如果有很多数据和您想对该数据执行更多查询操作。

      因此,尝试使用 IQueryable 属性,这样您至少可以更快地获取数据,然后对其进行一些操作。

      当然,检查您发送到 SQL 的查询/查询是什么。也许您正在搜索的列没有正确索引(!?)或者您在表中没有任何索引(?)。

      【讨论】:

        【解决方案5】:

        我会把赌​​注押在一个简单的子查询上,“自然地”代表所需的信息,就像这样

        var query =
            from r in context.StaffingResourceDatas
            where r.EmployeeId != null && !exclusionList.Contains(r.ResourceTitleId)
            select new ChartResourceModel
            {
                ResourceId = r.ResourceId,
                Division = r.ResourceDivision,
                Type = r.ResourceType,
                Dates = dates.Select(d => new ChartDateModel
                {
                    Date = d,
                    Available = context.StaffingForecasts.Where(f => 
                        f.ResourceId == r.ResourceId && f.Date == d).Sum(f => f.Hours) < 24
                }).ToList()
            };
        var sqlQuery = query.ToString();
        var result = query.ToList();
        

        【讨论】:

          猜你喜欢
          • 2011-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-15
          相关资源
          最近更新 更多