How to catch N+1 queries in EF Core before they hit production

Published: (April 12, 2026 at 06:45 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for How to catch N+1 queries in EF Core before they hit production

Your API can stay functionally correct while quietly getting slower.

  • No tests fail.
  • No alerts fire.
  • Everything “works”.

And then one day you realize an endpoint that used to execute 2 queries is now doing 15.

In EF Core, it’s surprisingly easy to introduce query regressions:

  • a small refactor changes how a projection is built
  • a navigation property is accessed differently
  • part of the query gets materialized too early
  • includes / relationships evolve over time

None of this breaks correctness. Your integration tests still pass because:

  • the response is correct
  • the database state is correct

But the query shape has changed. And that’s the part we usually don’t test.

Why this matters

Why this matters? This isn’t about premature optimization. It’s about catching issues like:

  • N+1 queries
  • unnecessary round‑trips
  • query explosions after refactors

These problems don’t show up as failing tests — they show up as slow endpoints in production. Detecting them in EF Core tests often involves writing a custom DbCommandInterceptor, wiring it into the test host, collecting executed SQL, and asserting on the count. It works, but it’s repetitive and low‑level; most teams end up copy‑pasting some version of this.

A simpler approach

I wanted something closer to what Django provides with assertNumQueries. So I wrapped the interceptor pattern into a small helper.

await using var guard = factory.TrackQueries();
var client = guard.CreateClient();

await client.GetAsync("/api/orders");

guard.AssertCount(atMost: 3);

That’s it.

  • No manual interceptor wiring
  • No log parsing
  • No test boilerplate

It simply tracks the number of SQL queries executed during a request.

When to use this

I don’t think this belongs in every test. For most endpoints, correctness is enough. It’s useful for:

  • list endpoints
  • dashboards / aggregates
  • endpoints with multiple relationships
  • any “hot path” where query shape matters

Basically, places where going from 2 queries → 10 queries is a real problem.

The idea

This isn’t about replacing integration tests. It’s about adding a lightweight guard against performance regressions—bugs that:

  • don’t break functionality
  • don’t fail tests
  • but hurt you in production

The helper is very small (~200 lines) and MIT licensed.

I’d be interested to know how others are solving this — or if you’re not testing for it at all.

0 views
Back to Blog

Related posts

Read more »