如何在 EF Core 中捕获 N+1 查询,防止它们进入生产环境

发布: (2026年4月13日 GMT+8 06:45)
3 分钟阅读
原文: Dev.to

Source: Dev.to

捕获 EF Core 中 N+1 查询的封面图片,防止它们进入生产环境

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 浏览
Back to Blog

相关文章

阅读更多 »