841

How can I do GroupBy Multiple Columns in LINQ

Something similar to this in SQL:

SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>

How can I convert this to LINQ:

QuantityBreakdown
(
    MaterialID int,
    ProductID int,
    Quantity float
)

INSERT INTO @QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM @Transactions
GROUP BY MaterialID, ProductID

12 답변


1083

Use an anonymous type.

Eg

group x by new { x.Column1, x.Column2 }


  • If you're new to grouping with anonymous types the use of the 'new' keyword in this example does the magic. - Chris
  • in case of mvc with nHibernate getting error for dll issues. Problem resolved by GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new { Key1 = key.Column1, Key2 = key.Column2 , Result = group.ToList() }); - Milan
  • I thought in this case the new objects would by compared by reference, so no match - no groupping. - HoGo
  • @HoGo anonymous typed objects implement their own Equals and GetHashCode methods which is used when grouping the objects. - Byron Carasco
  • A bit tough to visualize the output data structure when you're new to Linq. Does this create a grouping where the anonymous type is used as the key? - Jacques

661

Procedural sample

.GroupBy(x => new { x.Column1, x.Column2 })


  • What type is the object returned? - mggSoft
  • @MGG_Soft that would be an anonymous type - Alex
  • You get the 'Invalid anonymous type declarator because the syntax is slightly off it should be written as: .GroupBy(x => new { Column1 = x.Column1, Column2 = x.Column2 }) - Tom Maher
  • @Tom this should work the way it is. When you skip naming the fields of an anonymous type C# assumes you want to use the name of the finally accessed property/field from the projection. (So your example is equivalent to Mo0gles') - Crisfole
  • found my answer. I need to define a new entity (MyViewEntity) containing Column1 and Column2 properties and the return type is : IEnumerable<IGrouping<MyViewEntity, MyEntity>> and Grouping code snip is : MyEntityList.GroupBy(myEntity => new MyViewEntity { Column1 = myEntity.Column1, Column2 = myEntity.Column2 }); - Amir Chatrbahr

431

Ok got this as:

var query = (from t in Transactions
             group t by new {t.MaterialID, t.ProductID}
             into grp
                    select new
                    {
                        grp.Key.MaterialID,
                        grp.Key.ProductID,
                        Quantity = grp.Sum(t => t.Quantity)
                    }).ToList();


  • +1 - Thanks for the comprehensive example. The other answer's snippets are too short and without context. Also you show an aggregate function (Sum in this case). Very helpful. I find the use of an aggregate function (i.e., MAX, MIN, SUM, etc.) side-by-side with grouping to be a common scenario. - barrypicker
  • Here : stackoverflow.com/questions/14189537/… ,it is shown for a data table when grouping is based on a single column, whose name is known, but how can it be done if columns based on which the grouping is to be done has to be generated dynamically ? - b.g
  • This is really helpful in understanding the concept of grouping and applying aggregation over it. - rajibdotnet
  • Great example... just what i was looking for. I even needed the aggregate so this was the perfect answer even though I was looking for lambda i got enough from it to solve my needs. - Kevbo

128

For Group By Multiple Columns, Try this instead...

GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new 
{ 
  Key1 = key.Column1,
  Key2 = key.Column2,
  Result = group.ToList() 
});

Same way you can add Column3, Column4 etc.


  • That was very helpful and should get a lot more upvotes! Result contains all data sets linked to all columns. Thanks a lot! - j00hi
  • note: I had to use .AsEnumerable() instead of ToList() - GMan
  • Awesome, thanks for this. Here's my example. Note that GetFees returns an IQueryable<Fee> RegistryAccountDA.GetFees(registryAccountId, fromDate, toDate) .GroupBy(x => new { x.AccountId, x.FeeName }, (key, group) => new { AccountId = key.AccountId, FeeName = key.FeeName, AppliedFee = group.Sum(x => x.AppliedFee) ?? 0M }).ToList(); - Craig B
  • Is it possbile to get other columns from this query, which were not grouped? If there is array of object, I would like to get this object grouped by two columns, but get all properties from the object, not just those two columns. - FrenkyB

18

Since C# 7 you can also use value tuples:

group x by (x.Column1, x.Column2)

or

.GroupBy(x => (x.Column1, x.Column2))



16

You can also use a Tuple<> for a strongly-typed grouping.

from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
    LastName = grouping.Key.Item1,
    FirstName = grouping.Key.Item2,
    MiddleName = grouping.Key.Item3,
    DayCount = grouping.Count(), 
    AmountBilled = grouping.Sum(x => x.Rate),
}


  • Note: Creating a new Tuple is not supported in Linq To Entities - foolmoron

8

Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:

EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
                        .Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
                    var Dups = ClientProfiles.AsParallel()
                        .GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
                        .Where(z => z.Count() > 1)
                        .Select(z => z);


  • I couldn't do the Linq query "group c by new{c.Field<String>("Title"),c.Field<String>("CIF")}", and you saved me a lot of time!! the final query was: "group c by new{titulo= c.Field<String>("Title"),cif=c.Field<String>("CIF")} " - netadictos

5

var Results= query.GroupBy(f => new { /* add members here */  });


  • Adds nothing to the previous answers. - Mike Fuchs

2

.GroupBy(x => x.Column1 + " " + x.Column2)


  • Combined with Linq.Enumerable.Aggregate() this even allows for grouping by a dynamic number of properties: propertyValues.Aggregate((current, next) => current + " " + next). - Kai Hartmann
  • This is a better answer than anyone is giving credit for. It may be problematic if there could be instances of combinations where column1 added to column2 would equal the same thing for situations where column1 differs ("ab" "cde" would match "abc" "de"). That said, this is a great solution if you can't use a dynamic type because you are pre-constructing lambdas after the group by in separate expressions. - Brandon Barkley
  • "ab" "cde" should actually not match "abc" "de", hence the blank in between. - Kai Hartmann

2

group x by new { x.Col, x.Col}


2

.GroupBy(x => (x.MaterialID, x.ProductID))


  • Consider adding an explanation on how this code solves the problem. - Rosário Pereira Fernandes
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - From Review - Mamun

0

A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.

Example:

public class Key
{
    public string Prop1 { get; set; }

    public string Prop2 { get; set; }
}

This will compile but will generate one key per cycle.

var groupedCycles = cycles.GroupBy(x => new Key
{ 
  Prop1 = x.Column1, 
  Prop2 = x.Column2 
})

If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.

var groupedCycles = cycles.GroupBy(x => new 
{ 
  Prop1 = x.Column1, 
  Prop2= x.Column2 
})

foreach (var groupedCycle in groupedCycles)
{
    var key = new Key();
    key.Prop1 = groupedCycle.Key.Prop1;
    key.Prop2 = groupedCycle.Key.Prop2;
}

Linked


Related

Latest