dotnet add package NPlusOne.Analyzers
OrderService.cs(42,31): warning NP0001: 'IOrderRepository.GetByCustomerAsync' runs a query
('ToListAsync' in OrderRepository.GetByCustomerAsync) once per element of the foreach loop over 'customers'.
Load the rows for all elements up front with one query, Where(o => keys.Contains(o.CustomerId)),
and group them with ToLookup(o => o.CustomerId).
At build time
A Roslyn analyzer follows queries, lazy loads and saves through loops, LINQ lambdas, services, interfaces, mediators, mappers and serializers, and reports the ones that run once per element. Precision first: it warns only when it can prove the round trip.
In tests and requests
A runtime guard watches the SQL EF Core really sends inside a scope and fails a test, or flags a request, when the same query or lazy load repeats. It catches what no compiler can see.
Proven on a real database
Every one of the 143 known N+1 shapes in the catalogue is a test case run on SQLite: a reported case must send more SQL for more rows, a silent one the same amount, on EF Core 8, 9 and 10.
Three packages
Released together under one version, all on NuGet. Use the analyzer, the guard, or both.
NPlusOne.Analyzers
The Roslyn analyzer, its code fixes and the generator that follows calls across projects. A development dependency: nothing ships with your app.
dotnet add package NPlusOne.Analyzers
NPlusOne.EntityFrameworkCore
The runtime guard: UseNPlusOneGuard() on the DbContext, a scope per test, AssertNoNPlusOne(). Any test framework, no DI needed.
dotnet add package NPlusOne.EntityFrameworkCore
NPlusOne.AspNetCore
One guard scope per HTTP request, [NPlusOneAssert] for MVC and .WithNPlusOneAssert() for minimal APIs. Brings the guard with it.
dotnet add package NPlusOne.AspNetCore