Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to group by multiple generic linq expressions

I'm trying to use Linq expressions to construct a query, and am stuck trying to group by multiple columns. Say I have a basic collection:

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

I know you can group these using lambda expressions:

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");
}

This groups correctly as you can see:

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

But now, say I receive a collection of selectors to group against, that is passed to me as a parameter in my method, and that the entity type is generic:

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

Whoever's invoking the method would call like this:

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

How would I construct the group-by expression?

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

Edit

I updated above to hopefully clarify why I need the set of selectors.

Edit #2

Made the entity type in doLinq generic.

like image 620
McGarnagle Avatar asked Oct 26 '25 01:10

McGarnagle


2 Answers

Well, I'll assume you use linq-to-sql or something similar, so you need expression trees. If not there might be other possibilities.

Possible solutions I can see:

  • dynamic linq

see Vladimir Perevalovs answer.

  • constructing the whole groupby expression-tree manually

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

  • ugly workaround

Well, that's my departement :)

untested code:

 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");
     }
 }
like image 151
HugoRune Avatar answered Oct 28 '25 15:10

HugoRune


You should look at Dynamic Linq: http://blogs.msdn.com/b/mitsu/archive/2008/02/07/linq-groupbymany-dynamically.aspx

like image 33
Vladimir Perevalov Avatar answered Oct 28 '25 15:10

Vladimir Perevalov