Lesson 5 of 45 intermediate

LINQ and Deferred Execution

A query is a recipe, and it runs only when you ask for the food

Guess first

Pick an answer. You will see if you were right at the end of the lesson.

What happens when you write var q = list.Where(x => x > 3); and never use q?

Open interactive version (quiz + challenge)

Real-world analogy

Think of writing a recipe card. Writing "chop onions, fry for five minutes" does not cook anything. Cooking happens only when someone says dinner is served, and if they ask twice you cook twice. Here the recipe is the LINQ query and serving dinner is enumerating it. ToList() is cooking once and keeping the meal in a box.

What is it?

LINQ (Language Integrated Query) is a set of methods to filter, sort, group and reshape sequences. The same syntax works for lists, XML and databases. Most operators are deferred: they build a description of the work and do it only when you enumerate. Knowing when it runs, and where it runs, explains most slow or surprising LINQ code.

Real-world relevance

A dashboard page builds a filtered query, then shows the count and loops over the rows. Without a ToList(), that is two database queries. The other classic is a controller that calls _db.Orders.ToList() and then filters by user. That pulls a million rows into memory to return five.

Key points

Quick check

Answer from memory — scrolling up first makes it stick less.

You need the one user with this email, and your rules say there must never be two. Which method fits?

What is wrong with db.Orders.ToList().Where(o => o.UserId == 5)?

Code example

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var calls = 0;

// Only a recipe. Nothing runs yet, so calls is still 0.
var evens = numbers.Where(n =>
{
    calls++;
    return n % 2 == 0;
});

var count = evens.Count();   // runs the filter over all 6 items
var first = evens.First();   // runs it again, up to the first match
Console.WriteLine($"Filter calls: {calls}");   // 8, not 6

calls = 0;

// Run once, keep the result in memory
var cached = numbers.Where(n =>
{
    calls++;
    return n % 2 == 0;
}).ToList();

var count2 = cached.Count;   // no query runs
var first2 = cached[0];      // no query runs
Console.WriteLine($"Filter calls: {calls}");   // 6

Line-by-line walkthrough

  1. 1. calls counts how many times the filter function is really called.
  2. 2. The Where line only describes the filter. calls is still 0 after it.
  3. 3. Count() has to look at every item, so it runs the whole query. First() then runs it again from the start.
  4. 4. Count calls the filter 6 times, and First stops after the first even number, which is 2 calls. So calls is 8.
  5. 5. ToList() runs the filter one time over all 6 items and stores the 3 results in a real list.
  6. 6. Count and the index on the list read memory only. The filter is not called again.

Spot the bug

public List<Order> GetOrders(int userId)
{
    return _db.Orders
        .ToList()
        .Where(o => o.UserId == userId)
        .ToList();
}
Need a hint?
Where does the filtering happen, in the database or in your app?
Show answer
The first ToList() runs the query right away. It loads every row of Orders into memory, then Where filters in C#. On a large table this is slow and uses lots of memory. Fix: filter on the IQueryable first, <code>_db.Orders.Where(o => o.UserId == userId).ToList()</code>, so the database does the filtering. In an async method, use <code>await ... ToListAsync()</code> the same way.

Explain like I'm 5

Writing down what you want to buy is not shopping. You only shop when you go to the store. If you go to the store every time you read the list, you waste trips. Shop once and keep the bags.

Fun fact

Many LINQ methods stream: Take(3) on a huge sequence stops after three items. Others, such as OrderBy, must read everything first, because the last item might sort to the front.

Hands-on challenge

Say it out loud in 30 seconds: "What is deferred execution in LINQ, and why does calling ToList too early hurt with Entity Framework?" Then check: did you mention the query running only on enumeration, IQueryable turning into SQL, and loading the whole table?

More resources

Open interactive version (quiz + challenge) Download the whole course as PDF / Word

Lock it in

← Back to course: .NET + React Playbook