NPlusOne.EntityFrameworkCore
1.0.0
dotnet add package NPlusOne.EntityFrameworkCore --version 1.0.0
NuGet\Install-Package NPlusOne.EntityFrameworkCore -Version 1.0.0
<PackageReference Include="NPlusOne.EntityFrameworkCore" Version="1.0.0" />
<PackageVersion Include="NPlusOne.EntityFrameworkCore" Version="1.0.0" />
<PackageReference Include="NPlusOne.EntityFrameworkCore" />
paket add NPlusOne.EntityFrameworkCore --version 1.0.0
#r "nuget: NPlusOne.EntityFrameworkCore, 1.0.0"
#:package NPlusOne.EntityFrameworkCore@1.0.0
#addin nuget:?package=NPlusOne.EntityFrameworkCore&version=1.0.0
#tool nuget:?package=NPlusOne.EntityFrameworkCore&version=1.0.0
N+1
Finds N+1 query problems in .NET and EF Core code: at build time with a Roslyn analyzer, and in tests and requests with a runtime guard. Free and open source (MIT).
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).
Documentation: selcukgural.github.io/NPlusOne
Quick start
1. Add the analyzer to every project with EF Core code (EF Core 8, 9 or 10; any SDK since .NET 8):
dotnet add package NPlusOne.Analyzers
2. Build. N+1 queries show up as warnings, in the build and in the IDE, each with the rewrite that removes it:
dotnet build
3. Keep them out. Raise the rules to errors in .editorconfig:
[*.cs]
dotnet_diagnostic.NP0001.severity = error
dotnet_diagnostic.NP0002.severity = error
dotnet_diagnostic.NP0003.severity = error
dotnet_diagnostic.NP0005.severity = error
4. Catch the rest in tests. The runtime guard fails a test when the same query or lazy load repeats:
dotnet add package NPlusOne.EntityFrameworkCore
var options = new DbContextOptionsBuilder<ShopContext>().UseSqlite(connection).UseNPlusOneGuard().Options;
using var guard = NPlusOneGuard.Begin();
await new OrderService(new ShopContext(options)).ListAsync();
guard.AssertNoNPlusOne();
5. Watch requests in development with NPlusOne.AspNetCore: builder.Services.AddNPlusOneGuard() and
app.UseNPlusOneGuard().
The getting started guide walks through each step.
What it finds
N+1 hides in more places than a foreach with a query in it. The analyzer follows round trips through:
| Loops | for, foreach, await foreach, while, do, indexes and deconstructed elements |
| Callbacks | LINQ lambdas, method groups, List.ForEach, Parallel.ForEach, Task.WhenAll(ids.Select(...)), EF Core's ForEachAsync, FluentValidation RuleForEach, client-evaluated projections, your own iterating helpers |
| Calls | helpers, services, interfaces and virtual methods (to their implementations, across projects), extension and generic methods, delegates, compiled queries, getters and indexers, recursion |
| Libraries | MediatR and Mediator (handlers, pipeline behaviors, streams), AutoMapper, Mapster, Mapperly |
| Lazy loading | navigations read per element, checked against the query's Includes and AutoIncludes, also in ToString, Equals, conversions and patterns |
| Endpoints | MVC, minimal APIs, SignalR and serializers given lazily loaded entities, also inside pages and anonymous objects; OData and [JsonIgnore] respected |
| Writes | SaveChanges and transactions per element |
and it stays quiet where a loop is meant to make one round trip per iteration: batches, paging, polling, search loops, cached lookups, and anything EF Core translates into the same SQL statement.
Every one of the 132 shapes in the catalogue is a test that runs on SQLite and counts the SQL: a reported case must send more commands for more rows, a silent one the same number, on EF Core 8, 9 and 10.
Rules
| Rule | Default | |
|---|---|---|
| NP0001 | A query runs once per element (N+1 read) | Warning |
| NP0002 | A lazy-loaded navigation is read once per element (code fix: add the Include) |
Warning |
| NP0003 | SaveChanges runs once per element (code fix: move it after the loop) |
Warning |
| NP0004 | A recursive method runs a query on every call | Warning |
| NP0005 | An endpoint returns entities whose lazy navigations the serializer loads one by one | Warning |
| NP0006 | A data-access abstraction is called once per element and its implementation is not visible | Info |
| NP0007 | A mapper reads lazy navigations of each element by naming convention | Info |
Options (lazy loading, methods the analyzer cannot see into, call depth) are listed in configuration.
Packages
| Package | Use it for |
|---|---|
| NPlusOne.Analyzers | The analyzer, its code fixes and the summary generator that follows calls across projects. A development dependency. |
| NPlusOne.EntityFrameworkCore | The runtime guard: scopes in tests or anywhere, AssertNoNPlusOne, reports with normalized SQL and call sites, never parameter values. |
| NPlusOne.AspNetCore | One guard scope per request, [NPlusOneAssert] for MVC and .WithNPlusOneAssert() for minimal APIs. |
How it compares
Measured on the same proven cases, the analyzer finds 231 of 231 N+1 methods with no false warning; LinqContraband,
the static analyzer with the most N+1 rules we found, finds 53. At run time the guard here, EFCore.QueryGuard and DbEye
all see most cases; the guard alone also sees SQL that differs per element, transactions per element, and counts one
batched SaveChanges once. The corpus was written for this tool, so read the
comparison for what each number means.
Contributing
dotnet build and dotnet test (SDK pinned in global.json; the .NET 8, 9 and 10 runtimes run the tests). A missed
N+1 or a false warning starts as a corpus case; see
CONTRIBUTING.md and
how it works.
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
-
net8.0
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.10)
-
net9.0
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NPlusOne.EntityFrameworkCore:
| Package | Downloads |
|---|---|
|
NPlusOne.AspNetCore
ASP.NET Core integration for the N+1 runtime guard: one scope per request, with a middleware and an MVC filter. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 25 | 10/13/2026 |