Grouping Elements with GroupBy
GroupBy() partitions a sequence into clusters sharing a common key, returning an IEnumerable<IGrouping<TKey, TElement>> — each IGrouping<TKey, TElement> is itself an IEnumerable<TElement> that also exposes a Key property, so you can iterate the groups and, within each, iterate its members: foreach (var group in orders.GroupBy(o => o.CustomerId)) { Console.WriteLine(group.Key); foreach (var order in group) { ... } }. Unlike Where() or Select(), which process one element at a time, GroupBy() must buffer enough of the source to know which elements share a key before it can yield the first complete group.
Cricket analogy: Organizing a season's match results into clusters by team — every India match together, every Australia match together — mirrors GroupBy(match => match.Team), where each cluster's key is the team name.
Grouping with a Result Selector
GroupBy() has overloads accepting a resultSelector that runs once per group, transforming the (key, elements) pair directly into a final shape rather than requiring a separate Select() afterward: orders.GroupBy(o => o.CustomerId, (key, group) => new { CustomerId = key, Total = group.Sum(o => o.Amount) }). This is functionally equivalent to GroupBy(keySelector).Select(g => ...) but avoids materializing the intermediate IGrouping objects into a separately-selected sequence, which can be marginally more efficient and often reads more directly.
Cricket analogy: A tournament summary that groups matches by team and immediately computes each team's total wins, rather than listing raw match groups first, mirrors GroupBy() with a result selector producing the final shape in one step.
Grouping by Composite Keys
When a single field isn't enough to define a group, you can group by an anonymous type combining multiple fields: orders.GroupBy(o => new { o.CustomerId, o.Status }) creates one group per unique (CustomerId, Status) combination. Anonymous types automatically implement value-based equality — two anonymous type instances with the same property values in the same order are considered equal — which is exactly the equality semantics GroupBy() needs to correctly cluster elements by a composite key.
Cricket analogy: Grouping innings by both team AND venue, so 'India at Lord's' is a distinct group from 'India at the MCG', mirrors GroupBy(inning => new { inning.Team, inning.Venue }) using a composite key.
GroupBy vs. SQL GROUP BY
LINQ-to-Objects' GroupBy() differs meaningfully from SQL's GROUP BY: SQL requires every selected column to be either part of the GROUP BY clause or wrapped in an aggregate function, but LINQ's GroupBy() gives you the full member sequence inside each IGrouping, so you can access every original field of every element in a group, not just aggregated summaries. When GroupBy() runs against an IQueryable (e.g., Entity Framework), the provider must translate it into an equivalent SQL GROUP BY, which means accessing anything beyond the key or an aggregate inside the group can force the provider to either restructure the query or throw a translation exception.
Cricket analogy: SQL GROUP BY is like a scoreboard that only shows each team's total runs, while LINQ's GroupBy() is like having the full ball-by-ball log still accessible within each team's group, letting you inspect any individual delivery.
var orders = new List<Order>
{
new Order { Id = 1, CustomerId = 100, Status = "Shipped", Amount = 50.00m },
new Order { Id = 2, CustomerId = 100, Status = "Pending", Amount = 30.00m },
new Order { Id = 3, CustomerId = 200, Status = "Shipped", Amount = 75.00m },
};
// Basic grouping: one IGrouping per CustomerId
foreach (var group in orders.GroupBy(o => o.CustomerId))
{
Console.WriteLine($"Customer {group.Key}: {group.Count()} orders");
foreach (var order in group)
Console.WriteLine($" Order {order.Id}: {order.Amount:C}");
}
// Result selector: group + aggregate in one step
var customerTotals = orders.GroupBy(
o => o.CustomerId,
(key, group) => new { CustomerId = key, Total = group.Sum(o => o.Amount) });
// Composite key grouping
var byCustomerAndStatus = orders.GroupBy(o => new { o.CustomerId, o.Status });GroupBy() does not sort its output groups or the elements within each group — groups appear in the order their key was first encountered in the source, and elements within a group retain their original relative order. If you need sorted groups or sorted members within a group, chain OrderBy() explicitly after GroupBy().
Against Entity Framework and other IQueryable providers, GroupBy() followed by operations that touch non-aggregated, non-key columns inside the group (like group.Select(o => o.CustomerName)) may either translate into a client-evaluated fallback (pulling more data than expected) or throw a translation exception entirely, depending on the provider version. When working against a database, keep post-GroupBy() projections limited to the key and standard aggregate functions (Sum, Count, Average, Min, Max) to stay safely within what SQL's GROUP BY can express.
- GroupBy() partitions a sequence into IGrouping<TKey, TElement> clusters, each exposing a Key and its own enumerable members.
- The result-selector overload combines grouping and transformation in a single pass, avoiding a separate Select().
- Composite keys use anonymous types, which have value-based equality suited to GroupBy()'s clustering needs.
- Unlike SQL's GROUP BY, LINQ's GroupBy() keeps full access to every original element within each group, not just aggregates.
- Against IQueryable/database providers, non-aggregate access inside groups can fail to translate or force client evaluation.
- GroupBy() preserves first-encountered key order for groups and original relative order within each group.
- GroupBy() must buffer enough source data to determine group membership before yielding results.
Practice what you learned
1. What type does GroupBy() return for each individual group?
2. What is the purpose of GroupBy()'s result-selector overload?
3. How do you group elements by more than one field in LINQ?
4. How does LINQ's GroupBy() differ from SQL's GROUP BY in terms of accessible data?
5. What risk exists when using GroupBy() against an Entity Framework IQueryable and then accessing non-aggregate columns inside a group?
Was this page helpful?
You May Also Like
Ordering with OrderBy
Learn how LINQ's OrderBy and OrderByDescending sort sequences, how ThenBy chains secondary sort keys, and how sort stability and custom comparers work.
Joining with Join
Learn how LINQ's Join operator performs inner joins between two sequences, how GroupJoin enables left-outer-join patterns, and how composite keys and performance considerations work.
Filtering with Where
Learn how LINQ's Where operator filters sequences using predicates, how deferred execution affects when the filter actually runs, and how to combine multiple conditions safely.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics