4

背景

我有一个通过 LINQ-to-Entities 称为视图的 SQL 数据集。其目的是在信用报告中提供未结余额 30 天、60 天等未结余额。

在 StackOverflow 上为您提供一个示例表很难在此处格式化,但这里是 SQL SELECT 语句,它应该让您了解原始数据结构:

SELECT TOP 1000 [TransactionId]
      ,[IndustrySector]
      ,[DataContributorId]
      ,[ExperienceMonth]
      ,[ExperienceMonthText]
      ,[Balance]
      ,[ARCurrent]
      ,[AR1to30PD]
      ,[AR31to60PD]
      ,[AR61to90PD]
      ,[Ar91PlusPD]
      ,[WeightedDTP]
  FROM [BCC].[dbo].[vwTransactionExperienceDetail] 

现在,当我通过 LINQ 调用此视图时,最终目标是构造一个对象,该对象将作为 JSON 返回给请求客户端。结果对象需要是分组的层次结构Industry,然后是Contributors(报告的数据),最后是个人Reports。为此,以下 LINQ 查询可以正常工作并且速度非常快:

        /// <summary>
        /// Gets the 25 month experience detail report with summed parameters (balance, DTP, etc).
        /// </summary>
        /// <param name="id">The transaction id.</param>
        /// <returns>List&lt;ExperienceDetail&gt;</returns>
        public static List<ExperienceDetail> Get25MonthExperienceDetail_Sum(int id)
        {
            var db = new BCCEntities();
            return
                db.vwTransactionExperienceDetails.Where(te => te.TransactionId == id)
                  .GroupBy(g => g.IndustrySector)
                  .Select(i => new ExperienceDetail
                      {
                          Industry = i.Key,
                          NumberOfContributors = i.GroupBy(c => c.DataContributorId).Count(),
                          Balance = i.Sum(s => s.Balance),
                          OneToThirty = i.Sum(s => s.ARCurrent),
                          ThirtyOneToSixty = i.Sum(s => s.AR1to30PD),
                          SixtyOneToNinety = i.Sum(s => s.AR31to60PD),
                          NinetyOneToOneTwenty = i.Sum(s => s.AR61to90PD),
                          OneTwentyOnePlus = i.Sum(s => s.Ar91PlusPD),
                          DTP = (i.Sum(s => s.Balance) != 0) ? i.Sum(s => s.WeightedDTP) / i.Sum(s => s.Balance) : i.Sum(s => s.WeightedDTP),
                          Contributions = i.GroupBy(dc => dc.DataContributorId).Select(c => new Contribution
                              {
                                  Balance = c.Sum(s => s.Balance),
                                  OneToThirty = c.Sum(s => s.ARCurrent),
                                  ThirtyOneToSixty = c.Sum(s => s.AR1to30PD),
                                  SixtyOneToNinety = c.Sum(s => s.AR31to60PD),
                                  NinetyOneToOneTwenty = c.Sum(s => s.AR61to90PD),
                                  OneTwentyOnePlus = c.Sum(s => s.Ar91PlusPD),
                                  DTP = (c.Sum(s => s.Balance) != 0) ? c.Sum(s => s.WeightedDTP) / c.Sum(s => s.Balance) : c.Sum(s => s.WeightedDTP),
                                  ContributorId = c.Key,
                                  Reports = c.Select(r => new Report
                                  {
                                      DTP = (r.Balance != 0) ? r.WeightedDTP/r.Balance : r.WeightedDTP,
                                      ReportDate = r.ExperienceMonth,
                                      Balance = r.Balance,
                                      OneToThirty = r.ARCurrent,
                                      ThirtyOneToSixty = r.AR1to30PD,
                                      SixtyOneToNinety = r.AR31to60PD,
                                      NinetyOneToOneTwenty = r.AR61to90PD,
                                      OneTwentyOnePlus = r.Ar91PlusPD,
                                      ContributorId = r.DataContributorId,
                                      Industry = i.Key
                                  })
                              })
                      }).ToList();
        }

问题

我需要创建一个提供相同数据的附加服务,但仅限于每个贡献者报告的最近一个月DataContributorId( )。以下 LINQ 查询适用于此,但速度极慢 - 返回结果需要将近一分钟:

        /// <summary>
        /// Gets an experience detail report with summed parameters (balance, DTP, etc) for the most recent month.
        /// </summary>
        /// <param name="id">The transaction id.</param>
        /// <returns>List&lt;ExperienceDetail&gt;</returns>
        public static List<ExperienceDetail> Get25MonthExperienceDetail_MostRecentMonth(int id)
        {
            var db = new BCCEntities();
            db.CommandTimeout = 100000;
            return
                db.vwTransactionExperienceDetails.Where(te => te.TransactionId == id)
                  .OrderByDescending(o => o.ExperienceMonth)
                  .GroupBy(g => g.IndustrySector)
                  .Select(i => new ExperienceDetail
                  {
                      Industry = i.Key,
                      NumberOfContributors = i.GroupBy(c => c.DataContributorId).Count(),
                      Balance = i.GroupBy(dc => dc.DataContributorId).Sum(x => x.Select(z => z.Balance).FirstOrDefault()),
                      OneToThirty = i.Sum(s => s.ARCurrent),
                      ThirtyOneToSixty = i.Sum(s => s.AR1to30PD),
                      SixtyOneToNinety = i.Sum(s => s.AR31to60PD),
                      NinetyOneToOneTwenty = i.Sum(s => s.AR61to90PD),
                      OneTwentyOnePlus = i.Sum(s => s.Ar91PlusPD),
                      DTP = (i.Sum(s => s.Balance) != 0) ? i.Sum(s => s.WeightedDTP) / i.Sum(s => s.Balance) : i.Sum(s => s.WeightedDTP),
                      Contributions = i.GroupBy(dc => dc.DataContributorId).Select(c => new Contribution
                      {
                          Balance = c.Take(1).Sum(s => s.Balance),
                          OneToThirty = c.Take(1).Sum(s => s.ARCurrent),
                          ThirtyOneToSixty = c.Take(1).Sum(s => s.AR1to30PD),
                          SixtyOneToNinety = c.Take(1).Sum(s => s.AR31to60PD),
                          NinetyOneToOneTwenty = c.Take(1).Sum(s => s.AR61to90PD),
                          OneTwentyOnePlus = c.Take(1).Sum(s => s.Ar91PlusPD),
                          DTP = (c.Take(1).Sum(s => s.Balance) != 0) ? c.Take(1).Sum(s => s.WeightedDTP) / c.Take(1).Sum(s => s.Balance) : c.Take(1).Sum(s => s.WeightedDTP),
                          ContributorId = c.Key,
                          Reports = c.Select(r => new Report
                          {
                              DTP = (r.Balance != 0) ? r.WeightedDTP / r.Balance : r.WeightedDTP,
                              ReportDate = r.ExperienceMonth,
                              Balance = r.Balance,
                              OneToThirty = r.ARCurrent,
                              ThirtyOneToSixty = r.AR1to30PD,
                              SixtyOneToNinety = r.AR31to60PD,
                              NinetyOneToOneTwenty = r.AR61to90PD,
                              OneTwentyOnePlus = r.Ar91PlusPD,
                              ContributorId = r.DataContributorId,
                              Industry = i.Key
                          }).Take(1)
                      })
                  }).ToList();

        }

问题

如何在不影响性能的情况下查询此“最近报告的月份”结果集?在过去的几个小时里,我尝试隔离查询中花费最多时间的部分,但我似乎无法发现它。诚然,我不知道如何有效地分析复杂 LINQ 查询的性能问题,并愿意发表评论。

最终的问题是:是否有替代此 LINQ 查询的替代方法,它会产生相同的结果集而不会造成如此严重的性能损失?

提前致谢。

4

2 回答 2

1

在第二个查询中,您添加了:

Balance = i.GroupBy(dc => dc.DataContributorId).Sum(x => x.Select(z => z.Balance).FirstOrDefault()),

.OrderByDescending(o => o.ExperienceMonth)

尝试删除 groupBy 和 orderBy 以检查其中之一是否会导致性能问题,在这种情况下检查(并尝试)向此列添加索引(如果不存在)。

还要检查 SQL Profiler(如果 SQL Server 2005 或更低版本)或 SQL 扩展事件(如果 SQL Server 2008 或更高版本)。

您可以尝试使用LinqPAD工具来检查查询生成的 SQL DML

还有其他方法可以从数据库中获取数据:

  1. 在数据库上创建视图,并从 LINQ 中读取
  2. 编写实体 SQL查询
于 2013-01-04T17:09:56.423 回答
1

假设数据集相当小,我只会拉入所有月份, go ToList(),然后只过滤掉内存中最近的一个月。当查询变得复杂时,LINQ 可以做一些非常奇怪的事情。

于 2013-01-04T21:41:26.137 回答