Skip to content

ndcorder/EFQueryGuard

Repository files navigation

EFQueryGuard

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.

Installation

dotnet add package EFQueryGuard

That's it. The analyzer runs automatically during build with zero configuration.

Diagnostics

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

Examples

EFQ001 — N+1 Query Detection

// 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
}

EFQ002 — Missing AsNoTracking

// 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();

EFQ003 — Unbounded Query

// Warning: Unbounded query on 'Products' — add Take() or pagination
var all = ctx.Products.ToList();

// Fix:
var page = ctx.Products.Take(100).ToList();

EFQ004 — Cartesian Explosion

// 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();

EFQ005 — Sync over Async

// 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();
}

EFQ006 — Client Evaluation

// 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();

Configuration

Suppress individual rules via .editorconfig:

[*.cs]
dotnet_diagnostic.EFQ001.severity = none
dotnet_diagnostic.EFQ002.severity = warning

Or suppress inline:

#pragma warning disable EFQ003
var all = ctx.Products.ToList();
#pragma warning restore EFQ003

Supported EF Core Versions

EF Core 6, 7, 8, and 9. The analyzer detects patterns based on type names and method signatures, so it works across all supported versions.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for any new diagnostics
  4. Ensure dotnet test passes with zero failures
  5. Submit a pull request

Development

dotnet build                          # Build all
dotnet test                           # Run tests
dotnet build --no-incremental         # Clean build
dotnet format                         # Format code

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors