Intermediate · Collections, generics and LINQ · topic 25 of 43 · 35 min
LINQ in depth: grouping, joins, deferred execution, IQueryable
Explanation
GroupBy: totals per category
record Order(int Id, string City, decimal Amount, int CustomerId);
var orders = new List<Order>
{
new(1, "Pune", 500m, 10), new(2, "Delhi", 1200m, 11),
new(3, "Pune", 300m, 12), new(4, "Delhi", 800m, 10),
new(5, "Goa", 450m, 11)
};
var byCity = orders
.GroupBy(o => o.City)
.Select(g => new { City = g.Key, Orders = g.Count(), Total = g.Sum(o => o.Amount) });
foreach (var c in byCity)
Console.WriteLine($"{c.City}: {c.Orders} orders, Rs {c.Total}");GroupBy returns a sequence of groups. Each group is an IGrouping<TKey, TElement>: a Key plus the items in that group. You usually follow it with Select to make one summary row per group, exactly like GROUP BY in SQL.
Join: matching two collections
record Customer(int Id, string Name);
var customers = new List<Customer> { new(10, "Asha"), new(11, "Ravi"), new(12, "Meena") };
var lines = orders.Join(customers,
o => o.CustomerId, // key from orders
c => c.Id, // key from customers
(o, c) => $"#{o.Id} {c.Name} Rs {o.Amount}");
foreach (var l in lines) Console.WriteLine(l);
var same =
from o in orders
join c in customers on o.CustomerId equals c.Id
select new { o.Id, c.Name };SelectMany: flattening
var classes = new[]
{
new { Section = "A", Names = new[] { "Asha", "Ravi" } },
new { Section = "B", Names = new[] { "Meena" } }
};
var everyone = classes.SelectMany(c => c.Names);
Console.WriteLine(string.Join(", ", everyone)); // Asha, Ravi, MeenaDeferred execution: queries are recipes
A LINQ query like Where or Select does not run when you write it. It is a recipe that runs when someone asks for the results: a foreach, ToList, Count, First and so on. This is called deferred (delayed) execution. It means the query sees the data as it is at that moment, and it runs again every time you enumerate it.
var nums = new List<int> { 1, 2, 3 };
var big = nums.Where(n => n > 1); // nothing runs yet
nums.Add(4);
Console.WriteLine(big.Count()); // 3: the query ran now and saw 2, 3, 4
nums.Add(5);
Console.WriteLine(big.Count()); // 4: it ran again| Deferred (returns a sequence) | Immediate (runs now) |
|---|---|
| Where, Select, OrderBy, GroupBy, Join, Skip, Take, Distinct | ToList, ToArray, ToDictionary, Count, Sum, Average, Min, Max, First, Single, Any, All |
IEnumerable<T> versus IQueryable<T>
IEnumerable<T> is for data already in your program's memory. Its LINQ methods take ordinary delegates (compiled functions) and run them in your app. IQueryable<T> is for data somewhere else, usually a database. Its LINQ methods take expression trees: a data description of your lambda that a provider such as Entity Framework Core reads and translates to SQL. The filtering then happens inside the database, and only matching rows travel over the network.
// db.Orders is an IQueryable<Order> from Entity Framework Core (module 8)
var bigOrders = db.Orders
.Where(o => o.Amount > 1000) // becomes WHERE Amount > 1000 in SQL
.OrderBy(o => o.Id)
.ToList(); // runs ONE SQL query, returns only matching rows
var slow = db.Orders
.ToList() // loads EVERY order into memory first!
.Where(o => o.Amount > 1000); // then filters in C#SELECT ... FROM Orders WHERE Amount > 1000 ORDER BY IdCommon mistakes and fixes
- Calling ToList() or AsEnumerable() too early on a database query: everything is loaded into memory. Filter, sort and page first.
- Using a C# method EF Core cannot translate inside an IQueryable Where (like your own helper method): EF Core throws an error saying the query could not be translated. Rewrite with simple expressions or filter after loading a small result.
- Surprised that a query result changed: deferred execution saw new data. Use ToList() to take a snapshot.
- Forgetting that GroupBy keys are case-sensitive for strings: "pune" and "Pune" become two groups. Normalise first.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
LINQ in depth: grouping, joins, deferred execution, IQueryable · 1/7
In this lesson you will go deeper into LINQ: grouping, joining, and how queries really run.
Prefer a video by a teacher? Find free videos on “C# LINQ GroupBy Join deferred execution IEnumerable vs IQueryable” on YouTube ↗
In real life: A city-wise sales dashboard for a pizza chain
A pizza chain's head office in Mumbai wants, for each city, the number of orders, total sales and the best customer. LINQ grouping and joining build this report from the day's data in a few lines.
var report = orders
.GroupBy(o => o.City)
.Select(g => new
{
City = g.Key,
Sales = g.Sum(o => o.Amount),
TopCustomerId = g.GroupBy(o => o.CustomerId)
.OrderByDescending(cg => cg.Sum(o => o.Amount))
.First().Key
})
.Join(customers, r => r.TopCustomerId, c => c.Id,
(r, c) => $"{r.City}: Rs {r.Sales}, best customer {c.Name}")
.ToList();
report.ForEach(Console.WriteLine);Practice (0/4 done)
var list = new List<int> { 5, 10 }; var q = list.Select(x => x * 2); list.Add(20); Console.WriteLine(q.Sum()); What is printed?
Which of these runs a query immediately?
string[] w = { "apple", "avocado", "banana" }; How many groups does w.GroupBy(x => x[0]) make?
You have records Sale(string Product, string Category, int Qty, decimal Price). Write one LINQ query that returns, for each category, the category name, total quantity and revenue (Qty * Price), sorted by revenue from highest to lowest. Explain in one line why you would keep the Where/GroupBy before ToList if the data came from a database.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Generics: reusable, type-safe codeCollections, generics and LINQ · same module
- Collections: List, Dictionary, HashSet, Queue and StackCollections, generics and LINQ · same module
- Joining three or more tablesSQL and Databases · JOINs
- Grouping, joining and reshapingData Science, Machine Learning and AI · Cleaning, exploring and visualising data
- defer: clean up at the endGo Programming (backend and cloud) · Control flow and functions
- The C# and .NET interview questions asked most oftenInterviews, portfolio and careers
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does each group from GroupBy have?
Answer: A Key and the items in that group
IGrouping<TKey, TElement> has Key and is itself a sequence of items.
What is deferred execution?
Answer: A query runs only when its results are enumerated
The query is a recipe until enumerated.
Why is db.Orders.ToList().Where(...) usually bad?
Answer: It loads all rows into memory before filtering
Filter on the IQueryable so the database does the work.
IQueryable<T> LINQ methods receive lambdas as...
Answer: Expression trees that a provider can translate (e.g. to SQL)
Expression trees describe the code so EF Core can translate it.
In query syntax join, which keyword compares the keys?
Answer: equals
join ... on a equals b.
Which LINQ method filters items?Related
Answer: Where
Where keeps items for which the lambda returns true.
orders JOIN order_items: what does one row of the result stand for?Related
Answer: One order line
The grain becomes the more detailed table, order_items.
What does df.groupby("city")["amount"].sum() return?Related
Answer: The total amount for each city
Split by city, sum each group, combine into one value per city.
When does a deferred call run?Related
Answer: When the surrounding function returns
Deferred calls run as the function returns, including during a panic.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Collections, generics and LINQ”. The module exam comes next.
- Module 6 exam: Collections, generics and LINQ · 14 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Exceptions, delegates, events and async. 43 topics left in the course before the final exam.