Wat!
ada writes code

Tracking vs. no-tracking, and why your read endpoints are slow

“Why is this endpoint so slow? It only reads three tables.”

Reader — it does not read three tables. It reads three tables, and then it tracks every row it loaded, and then your using block exits, and then it does change-tracking on rows nobody changed, and then it walks the proxies, and then it returns.

EF Core's default behavior is change tracking. The DbContext keeps a copy of every row it loads, so that when you SaveChanges(), it can compute the delta. This is a great default for write paths — but it's a quiet, expensive default for read paths.

The fix is one line

return await db.Posts
    .AsNoTracking()
    .Where(p => p.Published)
    .OrderByDescending(p => p.CreatedAt)
    .Take(30)
    .ToListAsync(ct);

AsNoTracking() says: I am only reading. Do not snapshot. Do not track. Do not bother. On hot read paths, we routinely measure 2–5× speedups, and substantially less GC pressure.

When not to use it

  • When you intend to mutate the result and call SaveChanges on the same context.
  • When you load a graph that uses the identity map to dedupe references — though this is rare in modern code.

A nuance: split queries

If you .Include() two collection navigations, EF will, by default, generate one giant join. The result is a Cartesian explosion: N × M rows where you wanted N + M.

var nb = await db.Notebooks
    .AsNoTracking()
    .AsSplitQuery()
    .Include(n => n.Posts)
    .Include(n => n.Followers)
    .FirstAsync(n => n.Id == id);

AsSplitQuery() makes EF run three small queries instead of one enormous one. On a notebook with 200 posts and 5000 followers, the difference is the difference between fast and catastrophic.

Summary

Pattern Use when
AsNoTracking() Reading only. Almost always, in API endpoints.
Tracking (default) Reading and then mutating in the same DbContext.
AsSplitQuery() Multiple .Include() on collection navigations.

Make AsNoTracking() your default for read endpoints. Your p99 will thank you.


5

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.

An unhandled error has occurred. Reload 🗙