Lesson 20 / 26

Querying and Saving Data

Read with LINQ, avoid the N+1 problem and write changes with SaveChangesAsync.

LINQ becomes SQL

EF Core translates Where and Select into SQL, so filtering happens in the database. Use AsNoTracking() for read-only queries to skip change tracking, and always use the Async methods in web apps.

CRUD endpoints

Read cheap books without tracking, and add a book then save once. Related data is loaded explicitly with Include.

app.MapGet("/books", async (ShopDb db) =>
    await db.Books.AsNoTracking()
        .Where(b => b.Price < 500)
        .OrderBy(b => b.Title)
        .ToListAsync());

app.MapPost("/books", async (Book b, ShopDb db) =>
{
    db.Books.Add(b);
    await db.SaveChangesAsync();
    return Results.Created($"/books/{b.Id}", b);
});

The N+1 trap

Looping over orders and touching each order's items can fire one query per order. Load related rows in a single query with Include, or project to a DTO with Select. Log SQL in development to notice this early.

Quick check: What does AsNoTracking() do?

  • Deletes the rows
  • Encrypts the query
  • Disables caching of the table
  • Skips change tracking for a faster read-only query
Answer

Skips change tracking for a faster read-only query — Returned entities are not tracked, which saves memory and time when you will not update them.