Question
How can I group data by multiple columns in LINQ, similar to SQL GROUP BY Column1, Column2?
For example, given a SQL query like this:
SELECT MaterialID, ProductID, SUM(Quantity)
FROM @Transactions
GROUP BY MaterialID, ProductID
and a target structure like this:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
how can I write the equivalent logic in LINQ so that records are grouped by both MaterialID and ProductID, and the Quantity values are summed for each group?
Short Answer
By the end of this page, you will understand how to group records by more than one field in LINQ using C#. You will learn how anonymous objects are used as composite group keys, how to calculate aggregates like Sum, and how this pattern compares to SQL GROUP BY queries.
Concept
In LINQ, grouping by multiple columns means creating a composite key. Instead of grouping by a single value, you group by an object that contains several values.
In SQL, this is straightforward:
GROUP BY MaterialID, ProductID
In LINQ, the equivalent idea is usually written like this:
group item by new { item.MaterialID, item.ProductID }
or with method syntax:
.GroupBy(x => new { x.MaterialID, x.ProductID })
This matters because real data is often grouped by combinations of fields, not just one field. For example:
- sales by store and date
- logs by user and status
- transactions by material and product
- orders by customer and month
After grouping, you often calculate aggregate values such as:
SumCountAverageMinMax
In your case, the goal is to group transactions by and , then sum for each group.
Mental Model
Think of grouping like sorting receipts into labeled folders.
- If you group by one column, each folder gets one label, such as
MaterialID = 10. - If you group by multiple columns, each folder gets a label made of several parts, such as:
MaterialID = 10ProductID = 200
Every record with the same combination goes into the same folder.
Once the records are in each folder, you can total them up. In this case, you add all Quantity values inside each folder.
So the composite key is just the folder label made from multiple values.
Syntax and Examples
Query syntax
var result =
from t in transactions
group t by new { t.MaterialID, t.ProductID } into g
select new
{
g.Key.MaterialID,
g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
};
Method syntax
var result = transactions
.GroupBy(t => new { t.MaterialID, t.ProductID })
.Select(g => new
{
g.Key.MaterialID,
g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
});
Example with classes
using System;
using System.Collections.Generic;
using System.Linq;
public class Transaction
{
public int MaterialID { get; set; }
public int ProductID { get; set; }
public double Quantity { get; set; }
}
public
{
MaterialID { ; ; }
ProductID { ; ; }
Quantity { ; ; }
}
transactions = List<Transaction>
{
Transaction { MaterialID = , ProductID = , Quantity = },
Transaction { MaterialID = , ProductID = , Quantity = },
Transaction { MaterialID = , ProductID = , Quantity = },
Transaction { MaterialID = , ProductID = , Quantity = }
};
breakdown = transactions
.GroupBy(t => { t.MaterialID, t.ProductID })
.Select(g => QuantityBreakdown
{
MaterialID = g.Key.MaterialID,
ProductID = g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
})
.ToList();
Step by Step Execution
Consider this example:
var transactions = new List<Transaction>
{
new Transaction { MaterialID = 1, ProductID = 10, Quantity = 5 },
new Transaction { MaterialID = 1, ProductID = 10, Quantity = 3 },
new Transaction { MaterialID = 1, ProductID = 20, Quantity = 2 }
};
var result = transactions
.GroupBy(t => new { t.MaterialID, t.ProductID })
.Select(g => new
{
g.Key.MaterialID,
g.Key.ProductID,
Total = g.Sum(x => x.Quantity)
});
Step-by-step
1. Start with the input records
The list contains:
(1, 10, 5)(1, 10, 3)(1, 20, 2)
2. Apply GroupBy
.GroupBy(t => new { t.MaterialID, t.ProductID })
This creates groups based on these keys:
Real World Use Cases
Grouping by multiple columns appears often in real applications.
Inventory systems
Group stock movements by:
MaterialIDProductID
Then calculate total quantity moved.
Sales reporting
Group sales by:
StoreIdDate
Then sum revenue for each store per day.
API analytics
Group requests by:
UserIdStatusCode
Then count how many successful or failed requests each user made.
Order processing
Group order items by:
OrderIdProductId
Then total quantities for each product in each order.
Log analysis scripts
Group logs by:
ServiceNameSeverity
Real Codebase Usage
In real C# projects, developers often use multi-column grouping in these patterns:
Aggregation for DTOs
A common pattern is grouping raw records and projecting them into a DTO or view model.
var summary = transactions
.GroupBy(t => new { t.MaterialID, t.ProductID })
.Select(g => new QuantityBreakdown
{
MaterialID = g.Key.MaterialID,
ProductID = g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
});
Filtering before grouping
Developers often reduce the dataset first.
var summary = transactions
.Where(t => t.Quantity > 0)
.GroupBy(t => new { t.MaterialID, t.ProductID })
.Select(g => new QuantityBreakdown
{
MaterialID = g.Key.MaterialID,
ProductID = g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
});
Guarding against bad input
If a collection might be null, code often checks before grouping.
if (transactions == null)
{
return new List<QuantityBreakdown>();
}
Entity Framework queries
When used with EF or LINQ to Entities, this pattern is often translated into SQL by the provider. That makes it useful for database reporting without writing raw SQL.
Materializing at the end
Common Mistakes
1. Grouping by only one field by accident
Broken example:
var result = transactions
.GroupBy(t => t.MaterialID)
.Select(g => new
{
MaterialID = g.Key,
Quantity = g.Sum(x => x.Quantity)
});
This ignores ProductID, so different products for the same material get merged together.
Use this instead:
.GroupBy(t => new { t.MaterialID, t.ProductID })
2. Forgetting to project the grouped result
Broken example:
var groups = transactions.GroupBy(t => new { t.MaterialID, t.ProductID });
This gives you groups, but not the final shape you usually want.
Add Select(...) to create a useful result:
var result = transactions
.GroupBy(t => new { t.MaterialID, t.ProductID })
.Select(g => new
{
g.Key.MaterialID,
g.Key.ProductID,
Quantity = g.Sum(x => x.Quantity)
});
3. Trying to access grouped properties directly
Broken example:
Comparisons
| Concept | LINQ style | Best use case |
|---|---|---|
| Group by one column | .GroupBy(x => x.MaterialID) | When only one field defines the group |
| Group by multiple columns | .GroupBy(x => new { x.MaterialID, x.ProductID }) | When a combination of fields defines the group |
SQL GROUP BY | GROUP BY MaterialID, ProductID | Database-side grouping in SQL |
| LINQ query syntax | group x by ... into g select ... | Readable for SQL-like thinking |
| LINQ method syntax | .GroupBy(...).Select(...) | Common in fluent C# code |
Query syntax vs method syntax
Cheat Sheet
// Group by multiple columns
var result = items
.GroupBy(x => new { x.Col1, x.Col2 })
.Select(g => new
{
g.Key.Col1,
g.Key.Col2,
Total = g.Sum(x => x.Amount)
});
Key rules
- Use
new { ... }to group by multiple fields. - Access grouped key values with
g.Key.PropertyName. - Use
Select(...)afterGroupBy(...)to shape the result. - Use aggregate methods like:
SumCountAverageMinMax
- Query syntax and method syntax are equivalent in capability.
SQL to LINQ mapping
| SQL | LINQ |
|---|---|
GROUP BY A, B |
FAQ
How do I group by two columns in LINQ?
Use an anonymous object as the key:
.GroupBy(x => new { x.Column1, x.Column2 })
Is LINQ GroupBy equivalent to SQL GROUP BY?
Yes, conceptually. Both group rows by key values. In LINQ, you then project the grouped result into objects.
How do I sum values after grouping in LINQ?
Use Sum inside the projection:
.Select(g => new { Total = g.Sum(x => x.Quantity) })
Can I group by more than two columns?
Yes. Add more properties to the key:
.GroupBy(x => new { x.A, x.B, x.C })
Should I use query syntax or method syntax for grouping?
Either is fine. Method syntax is very common in C# projects, while query syntax can feel more familiar if you know SQL.
Why do I need g.Key after GroupBy?
Because each group stores its grouping values inside a key object. That key contains the fields you grouped by.
Does this work with Entity Framework?
Usually yes, as long as the LINQ provider can translate the query to SQL. This pattern is commonly supported.
Mini Project
Description
Build a small inventory summary tool that groups transaction records by MaterialID and ProductID, then calculates the total quantity for each pair. This mirrors a common reporting task in inventory, warehouse, and purchasing systems.
Goal
Create a LINQ query that produces a grouped quantity breakdown from a list of transactions.
Requirements
- Create a
Transactionclass withMaterialID,ProductID, andQuantityproperties. - Create a
QuantityBreakdownclass for the grouped output. - Add at least five sample transactions, including repeated
MaterialIDandProductIDcombinations. - Group the transactions by both
MaterialIDandProductID. - Sum the
Quantityvalues for each group and print the results.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.