2010-06-26 38 views
1

我有一個看似簡單的要求,但我無法弄清楚如何將它編寫爲只有一次往返服務器的查詢。Linq按月查詢聚合主動

基本上我有一個簡單的表

CREATE TABLE Item 
(
    id int not null identity(1,1), 
    create datetime not null, 
    close datetime --null means not closed yet 
); 

,我想要做的是在一定範圍內的時間(比如1/1/2010至2010/6/1),每月我需要的當月有效的項目數。如果某個項目是在該月份之前或之前創建的,並且該項目未關閉(即關閉爲空)或在該月份之後關閉,則該項目處於活動狀態。所以,我使用一個輔助方法翻譯那成LINQ表達式:

//just returns the first day of every month inbetween min and max (inclusive) 
private IEnumerable<DateTime> EnumerateMonths(DateTime Min, DateTime Max) 
{ 
    var curr = new DateTime(Min.Year, Min.Month, 1); 
    var Stop = new DateTime(Max.Year, Max.Month, 1).AddMonths(Max.Day == 1 ? 0 : 1); 
    while(curr < Stop) 
    { 
     yield return curr; 
     curr = curr.AddMonths(1); 
    } 
} 

public List<DataPoint> GetBacklogByMonth(DateTime min, DateTime max) 
{ 
    return EnumerateMonths(min, max) 
     .Select(m => new DataPoint 
         { 
          Date = m, 
          Count = DB.Items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1))) 
            .Count() 
          } 
      ).ToList(); 
} 

這完美的作品,除了每個Count是一個單獨的查詢,以便其超慢(每月往返),所以我的問題是,怎麼可能我重新構造這個查詢來完成一次往返服務器。

最初我考慮過按月彙總某種羣體,但因爲每個項目在很多不同的月份都可能是「積極」的,所以我認爲這不會起作用。

有什麼建議嗎?

回答

0

我恨回答我自己的問題,但這是我做的。

我真的需要做的是一個左表加入一個月表,然後做一個小組並計算每個月的項目數量。一個月份的正常分組將不起作用,因爲那麼這些項目只會在一個月內計算出來,而不是全部計入其中。於是我添加了一個表格Months,其中只包含了月份的第一個日期,並在其上進行了左連接。這個操作需要經常進行,我認爲值得添加一個表格。

繼承人的最終查詢:

 var joins = from m in DB.Months 
        from s in DB.Items 
        let nm = m.month.AddMonths(1) 
        where s.Create < nm && (!s.Close.HasValue || s.Close.Value >= nm) && m.month >= min && m.month <= max 
        select new { d = m.month, id = s.ID }; 
     var counts = from j in joins 
        group j by j.d into g 
        select new DataPoint { Date = g.Key, Count = g.Key > DateTime.Now ? 0 : g.Count() }; 

我還添加了一些代碼,以確保月份中有正確的行爲我的查詢。

0

只需先拉出您的物品,然後使用內存集合翻閱您的月份。我不知道我有你的標準數據庫查詢的權利,但它主要是:

var items = Db.Items.Where(s => s.Create <= min 
    && (!s.Close.HasValue || s.Close.Value >= max)).ToList(); 

return EnumerateMonths(min, max).Select(m => new DataPoint 
    { 
     Date = m, 
     Count = items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1))).Count() 
    }).ToList(); 
+0

這應該可行,但它可能會拉下幾千行,這可能是太多的網絡流量。生病嘗試一下,看看它是否可以接受。 – luke 2010-06-26 04:16:24

+0

如果行很大,你可以'Select()'只是'Create'和'Close'列。如果你使用的是MS SQL Sever,另一種方法是創建一個CLR函數來完成服務器端的工作。然後,您可以將此函數作爲LINQ-to-SQL映射中的方法添加。 http://msdn.microsoft.com/en-us/library/ms189876.aspx – Jay 2010-06-26 11:31:28

0

我會和傑伊說的一樣。我有類似的情況。在內存中進行排序/查詢將比多次訪問數據庫的速度更快。

如果您事先知道您只打算讀取,請將您的objectContext.Table設置爲MergeOption.NoTracking,並使用foreach循環進行迭代。

如果你還需要跟蹤,分離從DataContext的對象,你使用它後

var results = from t in DBContext.table select t where t.criteria=your select criteria 
foreach (var result in results) 
{ 
    DoSomething(result); 
    DbContext.Detach(result); 
} 

另外,如果你使用的是沒有跟蹤,你並不需要分離你的對象