2012-03-27 90 views
10

我對此做了一些研究,迄今爲止發現的最好的方法是在整個數據集上使用Asenumerable,以便在linq中將對象過濾到對象中比在數據庫上。我正在使用最新的EF。實體框架:按月高效分組

我的工作(但很慢)代碼:

 var trendData = 
      from d in ExpenseItemsViewableDirect.AsEnumerable() 
      group d by new {Period = d.Er_Approved_Date.Year.ToString() + "-" + d.Er_Approved_Date.Month.ToString("00") } into g 
      select new 
      { 
       Period = g.Key.Period, 
       Total = g.Sum(x => x.Item_Amount), 
       AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2) 
      }; 

這給了我幾個月格式YYYY-MM,總金額及平均量一起。然而,每次都需要幾分鐘的時間。

我的另一個解決方法是在SQL中做一個更新查詢,所以我有一個YYYYMM字段來本地分組。更改數據庫不是一個簡單的修復,但所以任何建議,將不勝感激。

我發現上述代碼思想的線程(http://stackoverflow.com/questions/1059737/group-by-weeks-in-linq-to-entities)提到'等到.NET 4.0'。最近有沒有什麼可以幫助解決這種情況?

回答

14

性能差的原因是整個表被提取到內存中(AsEnumerable())。您可以將然後按年份和月份這樣

var trendData = 
      (from d in ExpenseItemsViewableDirect 
      group d by new { 
          Year = d.Er_Approved_Date.Year, 
          Month = d.Er_Approved_Date.Month 
          } into g 
      select new 
      { 
       Year = g.Key.Year, 
       Month = g.Key.Month, 
       Total = g.Sum(x => x.Item_Amount), 
       AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2) 
      } 
     ).AsEnumerable() 
     .Select(g=>new { 
       Period = g.Year + "-" + g.Month, 
       Total = g.Total, 
       AveragePerTrans = g.AveragePerTrans 
     }); 

編輯

原始查詢,從我的反應,試圖做一個int和一個字符串,這是不之間的級聯由EF轉換爲SQL語句。我可以使用SqlFunctions類,但它得到的查詢很醜陋。所以我加AsEnumerable()分組製作後,這意味着EF將在服務器上執行組查詢,將得到年份,月份等,但是自定義投影是在對象上進行的(在之後AsEnumerable())。

+1

優秀,非常感謝阿德里安。我只做了一個快速測試,你的需要大約3.5秒,而原來的是5.2。長時間的延遲必須是我程序中的其他步驟。我感謝你的努力,你的代碼已經投入使用! – Glinkot 2012-03-27 10:49:12

+0

這不應該是被接受的答案。這只是一個解決方法! @cryss的答案給出了一個完美的結果。 – 2015-11-26 14:45:24

+0

優秀的解決方案,「按新增分組」非常強大,簡化了複雜的查詢。 – Jacob 2017-02-09 22:10:59

8

當按月來組我喜歡做這樣這個任務:

var sqlMinDate = (DateTime) SqlDateTime.MinValue; 

var trendData = ExpenseItemsViewableDirect 
    .GroupBy(x => SqlFunctions.DateAdd("month", SqlFunctions.DateDiff("month", sqlMinDate, x.Er_Approved_Date), sqlMinDate)) 
    .Select(x => new 
    { 
     Period = g.Key // DateTime type 
    }) 

,因爲它使在分類結果的日期時間類型。

+1

這應該是最初的答案!簡單的MS-SQL月份分離轉換爲LINQ到實體! – 2015-11-26 14:42:51

2

類似於cryss寫的,我爲EF做了以下工作。請注意,我們必須使用EntityFunctions來調用EF支持的所有數據庫提供者。 SqlFunctions只適用於SQLServer。

var sqlMinDate = (DateTime) SqlDateTime.MinValue; 

(from x in ExpenseItemsViewableDirect 
let month = EntityFunctions.AddMonths(sqlMinDate, EntityFunctions.DiffMonths(sqlMinDate, x.Er_Approved_Date)) 
group d by month 
into g 
select new 
{ 
Period = g.Key, 
    Total = g.Sum(x => x.Item_Amount), 
    AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2) 
}).Dump(); 

生成的SQL的味道(從類似的架構):

-- Region Parameters 
DECLARE @p__linq__0 DateTime2 = '1753-01-01 00:00:00.0000000' 
DECLARE @p__linq__1 DateTime2 = '1753-01-01 00:00:00.0000000' 
-- EndRegion 
SELECT 
1 AS [C1], 
[GroupBy1].[K1] AS [C2], 
[GroupBy1].[A1] AS [C3] 
FROM (SELECT 
    [Project1].[C1] AS [K1], 
    FROM (SELECT 
     DATEADD (month, DATEDIFF (month, @p__linq__1, [Extent1].[CreationDate]), @p__linq__0) AS [C1] 
     FROM [YourTable] AS [Extent1] 
    ) AS [Project1] 
    GROUP BY [Project1].[C1] 
) AS [GroupBy1]