A Roslyn analyzer that catches EF Core performance traps at compile time.
EF Core performance problems — N+1 queries, missing AsNoTracking(), unbounded queries, cartesian explosions, and synchronous database calls in async contexts — are among the most common .NET issues. EFQueryGuard detects these patterns during compilation with clear diagnostics and suggested fixes.
dotnet add package EFQueryGuardThat's it. The analyzer runs automatically during build with zero configuration.
| ID | Severity | Description |
|---|---|---|
| EFQ001 | Warning | Navigation property accessed in loop without Include() — causes N+1 queries |
| EFQ002 | Info | Read-only query missing AsNoTracking() |
| EFQ003 | Warning | Unbounded query without Take() or pagination |
| EFQ004 | Warning | Multiple collection Include() calls cause cartesian explosion |
| EFQ005 | Warning | Synchronous query method in async method |
| EFQ006 | Info | Expression may trigger client-side evaluation |
// Warning: Navigation 'Order.Items' accessed in loop without Include()
foreach (var order in ctx.Orders.ToList())
{
var items = order.Items; // N+1! Each iteration hits the database
}
// Fix: add Include()
foreach (var order in ctx.Orders.Include(o => o.Items).ToList())
{
var items = order.Items; // OK — loaded eagerly
}// Info: Read-only query on 'Products' missing AsNoTracking()
var products = ctx.Products.Where(p => p.Active).ToList();
// Fix:
var products = ctx.Products.AsNoTracking().Where(p => p.Active).ToList();// Warning: Unbounded query on 'Products' — add Take() or pagination
var all = ctx.Products.ToList();
// Fix:
var page = ctx.Products.Take(100).ToList();// Warning: Multiple Include() calls on 'Orders' may cause cartesian explosion
var orders = ctx.Orders
.Include(o => o.Items)
.Include(o => o.Notes)
.ToList();
// Fix: use AsSplitQuery()
var orders = ctx.Orders
.Include(o => o.Items)
.Include(o => o.Notes)
.AsSplitQuery()
.ToList();// Warning: Synchronous call 'ToList' in async method — use 'ToListAsync'
public async Task<List<Product>> GetProducts(TestContext ctx)
{
return ctx.Products.ToList(); // blocks the thread!
}
// Fix:
public async Task<List<Product>> GetProducts(TestContext ctx)
{
return await ctx.Products.ToListAsync();
}// Info: Call to 'IsValid' may trigger client-side evaluation
var products = ctx.Products.Where(p => IsValid(p.Name)).ToList();
// Fix: use inline expression or EF-translatable methods
var products = ctx.Products.Where(p => p.Name.Length > 3).ToList();Suppress individual rules via .editorconfig:
[*.cs]
dotnet_diagnostic.EFQ001.severity = none
dotnet_diagnostic.EFQ002.severity = warningOr suppress inline:
#pragma warning disable EFQ003
var all = ctx.Products.ToList();
#pragma warning restore EFQ003EF Core 6, 7, 8, and 9. The analyzer detects patterns based on type names and method signatures, so it works across all supported versions.
- Fork the repository
- Create a feature branch
- Write tests for any new diagnostics
- Ensure
dotnet testpasses with zero failures - Submit a pull request
dotnet build # Build all
dotnet test # Run tests
dotnet build --no-incremental # Clean build
dotnet format # Format codeMIT