2012-04-13 69 views
3

我試圖使用Linq表達式來構造一個查詢,並且試圖按多列進行分組。說我有一個基本的集合:如何按多個通用linq表達式進行分組

IEnumerable<Row> collection = new Row[] 
{ 
    new Row() { Col1 = "a", Col2="x" }, 
    new Row() { Col1 = "a", Col2="x" }, 
    new Row() { Col1 = "a", Col2="y" }, 
}; 

我知道你可以組這些使用lambda表達式:

foreach (var grp in collection.GroupBy(item => new { item.Col1, item.Col2 })) 
{ 
    Debug.Write("Grouping by " + grp.Key.Col1 + " and " + grp.Key.Col2 + ": "); 
    Debug.WriteLine(grp.Count() + " rows"); 
} 

這組正確,你可以看到:

Grouping by a and x: 2 rows 
Grouping by a and y: 1 rows 

但現在,說我收到一組選擇器進行分組,這是作爲我的方法中的一個參數傳遞給我的,並且實體類型是通用的:

void doLinq<T>(params Expression<Func<T,object>>[] selectors) 
{ 
    // linq stuff 
} 

誰真實調用方法將調用這樣的:

doLinq<Row>(entity=>entity.Col1, entity=>entity.Col2); 

我將如何通過組表達構建?

foreach (var grp in collection.GroupBy(
     item => new { 
      // selectors?? 
     })) 
{ 
    // grp.Key. ?? 
} 

編輯

我上面希望澄清爲什麼我需要設定選擇的更新。

編輯#2

作出的實體doLinq泛型類型。

+1

好像你應該能夠編寫一個將多個'Expression >'結合成一個'Expression < Func >'返回一個元組或者數組或者其他值。它看起來像你正在使用LINQ到SQL,但我不知道有關到SQL的翻譯知道你是否可以用可翻譯的方式做到這一點,但是。 – phoog 2012-04-13 20:15:37

+0

@phoog它看起來像你應該能夠將它們組合成一個單一的表達式 - 我仍然不知道該怎麼做。我最終結合了表達式的結果,而不是每行。 – McGarnagle 2012-04-21 20:28:21

+0

您應該將解決方案作爲新答案發布,而不是編輯問題。 – svick 2012-04-21 20:30:03

回答

0

該解決方案爲我工作。它涉及兩部分:

  • 創建一個分組對象(我不恰當地實現爲object [])給出了行值和一​​組選擇器。這涉及一個lambda表達式,它可以編譯和調用行項目上的每個選擇器。
  • 實施IEquality爲分組對象的類型(對我來說這是的IEqualityComparer)。

第一部分

foreach (System.Linq.IGrouping<object[], T> g in collection.GroupBy(
    new Func<T, object[]>(
     item => selectors.Select(sel => sel.Compile().Invoke(item)).ToArray() 
    ), 
    new ColumnComparer() 
) 
{ ... } 

第二部分

public class ColumnComparer : IEqualityComparer<object[]> 
{ 
    public bool Equals(object[] x, object[] y) 
    { 
     return Enumerable.SequenceEqual(x, y); 
    } 

    public int GetHashCode(object[] obj) 
    { 
     return (string.Join("", obj.ToArray())).GetHashCode(); 
    } 
} 

這適用於基本的LINQ,和LINQ爲MySQL連接器。哪些其他Linq提供者,以及哪些表達式類型適用於其他問題...

+1

我認爲這將查詢所有行的數據庫,然後在本地執行分組。因此,這不會對LINQ到SQL與該查詢被轉換爲T-SQL一個MS-SQL服務器的工作,你就需要爲表達式樹。儘管如此,即使在linq-to-sql中,您也可以在GroupBy()之前預先添加.AsEnumerable(),以強制在本地執行分組 – HugoRune 2012-04-21 22:02:20

1
+0

非常有趣和有用的鏈接,但它不是我想要的。 Mitsu在分層關係中進行分組,但每個分組都是由一個關鍵字完成的 - 也就是按國家分組,然後按照每個分組,按城市分組。我想要有一個單一級別的分組,但密鑰中有多個屬性,例如按國家和年齡段分組(在我的示例中爲Col1和Col2)。 – McGarnagle 2012-04-13 18:59:02

+0

對不起,我錯過了目標:)這裏有我的意思是溶液中的鏈接類似的問題:http://stackoverflow.com/questions/3929041/dynamic-linq-groupby-multiple-columns通過動態的LINQ我使用基於字符串的建議linq接口。 – 2012-04-13 19:04:21

1

好吧,我會假設你使用LINQ到SQL或類似的東西,所以你需要表達式樹。如果沒有,可能還有其他可能性。

可能的解決方案,我可以看到:

  • 動態LINQ

看到弗拉基米爾Perevalovs答案。

  • 構建整個GROUPBY表達式樹手動

看到 http://msdn.microsoft.com/en-us/library/bb882637.aspx

  • 醜陋的解決方法

嗯,這是我的DEPARTEMENT :)

未經測試的代碼:

void doLinq(params string[] selectors) // checking two expressions for equality is messy, so I used strings 
    foreach (var grp in collection.GroupBy(
      item => new { 
       Col1 = (selectors.Contains("Col1") ? item.Col1 : String.Empty), 
       Col2 = (selectors.Contains("Col2") ? item.Col2 : String.Empty) 
       // need to add a line for each column :(
      })) 
    { 
      string[] grouping = (new string[]{grp.Key.Col1, grp.Key.Col2 /*, ...*/ }).Where(s=>!s.IsNullOrEmpty()).ToArray(); 
      Debug.Write("Grouping by " + String.Join(" and ", grouping)+ ": "); 
      Debug.WriteLine(grp.Count() + " rows"); 
    } 
} 
+0

我意識到我最初簡化了這個問題 - 它掩蓋了我想要做的事情。 「Row」類型實際上是通用的(參見上面的更新),這似乎排除了#1和#3。 #2看起來很有趣,我正在檢查它... – McGarnagle 2012-04-15 17:11:51

1

我有LINQ到SQL的極其有限的知識,但它是非常重要的裏面有什麼的GroupBy?因爲如果不是,你可以推出你自己的keySelector。無論如何,我都用的SQL Server CE和SQL Server Express,這似乎工作試了一下:

using System; 
using System.Linq; 
using System.Collections.Generic; 
using System.Data.Linq; 
using System.Linq.Expressions; 

namespace ConsoleApplication1 { 
    class Props { 
     public List<object> list = new List<object>(); 
     public override bool Equals(object obj) { 
      return Enumerable.SequenceEqual(list, (obj as Props).list); 
     } 
     public override int GetHashCode() { 
      return list.Select(o => o.GetHashCode()).Aggregate((i1, i2) => i1^i2); 
     } 
    } 
    class Program { 
     static void Main(string[] args) { 
      Lol db = new Lol(@"Data Source=.\SQLExpress;Initial Catalog=Lol;Integrated Security=true"); 
      db.Log = Console.Out; 
      doLinq(db.Test, row => row.Col1, row => row.Col2); 
      Console.ReadLine(); 
     } 
     static void doLinq<T>(Table<T> table, params Func<T, object>[] selectors) where T : class { 
      Func<T, Props> selector = item => { 
       var props = new Props(); 
       foreach (var sel in selectors) props.list.Add(sel(item)); 
       return props; 
      }; 
      foreach (var grp in table.GroupBy(selector)) { 
       Console.Write("Grouping by " + string.Join(", ", grp.Key.list) + ": "); 
       Console.WriteLine(grp.Count() + " rows"); 
      } 
     } 
    } 
} 

洛爾數據庫有一個表「測試」三行。輸出是這樣的:

SELECT [t0].[Col1], [t0].[Col2] 
FROM [dbo].[Test] AS [t0] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1 

Grouping by a, x: 2 rows 
Grouping by a, y: 1 rows 

我檢查了查詢,似乎LINQ到SQL足夠聰明,不用於GROUPBY生成SQL時,它不能,那麼它會通過的所有行迭代表,然後將它們分組在客戶端上。

編輯:爲了完成的目的,增加了一些小部分,連接字符串現在假定爲Sql Server Express。

+0

巧妙,但我無法得到它的工作...我不知道爲什麼。就這樣,「Props.Equals」方法總是在我的測試用例中返回true;它似乎在比較Linq表達式而不是評估。你不需要在那裏使用「Expression.Compile()」嗎? – McGarnagle 2012-04-21 20:25:06

+0

嘛'GroupBy'組織'Props'到一個哈希表,所以'Props.Equals'將只要求對象進行'道具。GetHashCode'返回相同的值,這意味着它們很可能已經相等。因此,「Props.Equals」本身總是返回真實並不是一個問題。你也不需要在代碼中的任何地方使用'Expressions',因爲整個分組發生在客戶端,生成的sql是一個簡單的從表中選擇的select。我也用SQL Server Express在相同的結果上試了一下。 – user1096188 2012-04-22 06:35:52

相關問題