优化非常大的LINQ查询

时间:2015-11-02 20:16:50

标签: c# performance entity-framework linq

我需要编写一个/多个LINQ查询来获取我将为报告显示的一些数据。

我需要使用的表是StaffingResources,StaffingForecastEvents(将StaffingResource链接到项目)和StaffingForecasts(链接到StaffingForecastEvents并包含每周的小时数)。每个StaffingResource可以有0多个StaffingForecastEvents,而StaffingForecastEvent可以有0多个StaffingForecasts。

我需要编写LINQ查询,对于每个资源,它将包含他们拥有ForecastEvents的所有项目,以及每个项目在给定日期范围内的所有预测(12周或6个月) 。这是我到目前为止所做的,而且运行速度很慢。

// Get date ranges
var dates = new List<DateTime>();
var startDate = range == (int)RangeTypes.WEEKLY 
    ? DateTime.Today.AddDays(DayOfWeek.Monday - DateTime.Today.DayOfWeek) 
    : new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
for (var i = 0; i < (range == (int)RangeTypes.WEEKLY ? 4 : 6); i++)
{
    dates.Add(range == (int)RangeTypes.WEEKLY ? startDate.AddDays(i * 7) : startDate.AddMonths(i));
}
var endDate = dates[dates.Count-1];

// Get resources
var resources = from r in context.StaffingResourceDatas
                where r.EmployeeId.HasValue
                   && (resourceIds.Count == 0 || resourceIds.Contains(r.EmployeeId.Value))
                   && (resourceDivisions.Count == 0 || resourceDivisions.Contains(r.ResourceDivisionId))
                   && (resourceTitles.Count == 0 || resourceTitles.Contains(r.ResourceTitleId))
                   && (resourceLocations.Count == 0 || resourceLocations.Contains(r.ResourceLocationId))
                   && (supervisors.Count == 0 || supervisors.Contains(r.ReportsToId))
                   && (showAllResources || (!showAllResources && !exclusionList.Contains(r.ResourceTitleId)))
                join fe in context.StaffingForecastEvents
                   .Include(x => x.StaffingForecasts)
                   .Include(x => x.StaffingUser)
                   .Include(x => x.StaffingUser1)
                   .Include(x => x.StaffingForecasts.Select(y => y.StaffingUser))
                   .Include(x => x.StaffingForecasts.Select(y => y.StaffingUser1))
                 on r.ResourceId equals fe.ResourceId into g1
                from fe in g1.DefaultIfEmpty()
                join p in context.StaffingProjectDatas on fe.JobNumber equals p.JobNumber into g2
                from p in g2.DefaultIfEmpty()
                group new { ForecastEvent = fe, Project = p } by r into g3
                select new
                {
                    ResourceId = g3.Key.ResourceId,
                    Name = g3.Key.ResourceName,
                    Title = g3.Key.ResourceTitle,
                    Division = g3.Key.ResourceDivision,
                    Location = g3.Key.ResourceLocation,
                    AvailableDate = g3.Key.AvailableDate,
                    SupervisorEmail = g3.Key.ManagerEmail,
                    Projects = g3.Where(p => p.ForecastEvent != null).Select(p => new
                    {
                        JobNumber = p.ForecastEvent.JobNumber,
                        Description = p.Project.ProjectDescription,
                        Name = p.Project.ProjectName,
                        Division = p.Project.ProjectDivision,
                        ProjectManager = p.Project.PMName,
                        Notes = p.ForecastEvent.Notes,
                        LogDate = p.ForecastEvent.LogDate,
                        LogUser = p.ForecastEvent.StaffingUser.Name,
                        AckDate = p.ForecastEvent.AcknowledgeDate,
                        AckUser = p.ForecastEvent.StaffingUser1 != null ? p.ForecastEvent.StaffingUser1.Name : null,
                        Usages = dates.Select(d => new
                        {
                            Date = d,
                            Hours = (range == (int)RangeTypes.WEEKLY)
                               ? (p.ForecastEvent.StaffingForecasts.Where(f => f.Date == d).Any() ? p.ForecastEvent.StaffingForecasts.Where(f => f.Date == d).Sum(f => f.Hours) : 0)
                               : (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).Any() ? p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).Sum(f => f.Hours) : 0),
                            LogDate = (range == (int)RangeTypes.WEEKLY)
                                // Get acknowledge or log date for week
                               ? (p.ForecastEvent.StaffingForecasts.Where(f => f.Date == d).Any()
                                   ? ((p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).AcknowledgeDate) != null)
                                       ? (p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).AcknowledgeDate)
                                       : (p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).LogDate)
                                   : null)
                                // Get acknowledge or log date for most recent forecast for month
                               : (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).Any()
                                   ? ((p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).OrderByDescending(f => f.LogDate).FirstOrDefault().AcknowledgeDate) != null)
                                       ? (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).OrderByDescending(f => f.LogDate).FirstOrDefault().AcknowledgeDate)
                                       : (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).Max(f => f.LogDate))
                                   : null),
                            LogUser = (range == (int)RangeTypes.WEEKLY)
                                // Get acknowledge or log user for week
                               ? (p.ForecastEvent.StaffingForecasts.Where(f => f.Date == d).Any()
                                   ? ((p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).AcknowledgeDate) != null)
                                       ? (p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).StaffingUser1.Name)
                                       : (p.ForecastEvent.StaffingForecasts.FirstOrDefault(f => f.Date == d).StaffingUser.Name)
                                   : null)
                                // Get acknowledge or log user for most recent forecast for month
                               : (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).Any()
                                   ? ((p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).OrderByDescending(f => f.LogDate).FirstOrDefault().AcknowledgeDate) != null)
                                       ? (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).OrderByDescending(f => f.LogDate).FirstOrDefault().StaffingUser1.Name)
                                       : (p.ForecastEvent.StaffingForecasts.Where(f => f.Date.Year == d.Year && f.Date.Month == d.Month).OrderByDescending(f => f.LogDate).FirstOrDefault().StaffingUser.Name)
                                   : null),
                        })
                    })
                };

我还需要为每个日期范围获取每个资源的总计。

我觉得瓶颈可能出现在所有&#34; Where&#34; s循环过去的日期,但我不知道还能做什么。有什么想法吗?

1 个答案:

答案 0 :(得分:1)

作为起点,您可以使用IQueryable<T>将其拆分为多个表达式。它还可以帮助您简化where子句。这看起来像是:

var queryable = context.StaffingResourceDatas.Where(r => r.EmployeeId.HasValue);
if(resourceIds.Any())
{
    queryable = queryable.Where(r => resourceIds.Contains(r.EmployeeId.Value))
}

请注意,仅当resourceIds不为空时,这将生成与resourceIds过滤相关的SQL,从而可能节省在生成的查询本身中检查它的开销。

您可以使用与此类似的方式编写其余过滤器。另请注意,在您调用ToList()之前,查询将不会执行。因此,您可以继续添加任意数量的子句,直到您完成构建它。

但是在一天结束时你可能会考虑在原始SQL中编写这个,因为它只是,你知道,这是巨大的。