Documentation
EF Core Async Query Analyzer
Use LinqContraband as an EF Core async query analyzer for sync-over-async EF Core calls, missing cancellation tokens, SaveChangesAsync loops, and async stream buffering.
EF Core Async Query Analyzer
LinqContraband helps teams review async EF Core query paths before they reach production. It catches synchronous
database calls inside async methods, missing cancellation tokens on async EF APIs, SaveChangesAsync inside loops, and
async stream buffering that should stay streaming.
For the focused missing-token workflow, use the EF Core CancellationToken analyzer guide.
Install the official NuGet package:
dotnet add package LinqContraband
Why Async EF Core Calls Need Review
Async code can still block request threads if it calls synchronous EF Core APIs. It can also ignore request cancellation or buffer an async stream into memory before looping once.
public async Task<List<User>> ActiveUsers(CancellationToken cancellationToken)
{
return db.Users
.Where(user => user.IsActive)
.ToList(); // LC008: sync-over-async
}
Prefer the async EF Core API and pass the token that represents the current operation:
public async Task<List<User>> ActiveUsers(CancellationToken cancellationToken)
{
return await db.Users
.Where(user => user.IsActive)
.ToListAsync(cancellationToken);
}
Rules Covered By The Async Guide
| Rule | What it catches | Why it matters |
|---|---|---|
| LC008: sync-over-async | Synchronous EF Core query, save, find, and bulk APIs inside async contexts. | Blocking database I/O ties up request or worker threads that could be serving other work. |
| LC026: missing CancellationToken | Async EF Core calls that omit a token, pass default, or pass CancellationToken.None while a usable token is in scope. |
Cancelled requests and stopping workers should not leave database queries running unnecessarily. See the EF Core CancellationToken analyzer guide for the focused rollout path. |
| LC043: async stream buffering | Immediate ToListAsync or ToArrayAsync buffering of an IAsyncEnumerable<T> before a single loop. |
Buffering loses the memory and latency benefits of streaming. |
| LC010: SaveChanges inside loop | SaveChanges or SaveChangesAsync inside for, foreach, while, and related loop shapes. |
Per-item commits create repeated transactions and partial-progress states. |
Common Async EF Core Problems
Sync query inside async method
public async Task<int> CountUsers()
{
return db.Users.Count(); // LC008
}
public async Task<int> CountUsers(CancellationToken cancellationToken)
{
return await db.Users.CountAsync(cancellationToken);
}
Async query without cancellation
public async Task<User?> FindUser(int id, CancellationToken cancellationToken)
{
return await db.Users.FirstOrDefaultAsync(user => user.Id == id); // LC026
}
public async Task<User?> FindUser(int id, CancellationToken cancellationToken)
{
return await db.Users.FirstOrDefaultAsync(user => user.Id == id, cancellationToken);
}
Async stream buffering before one loop
var users = await stream.ToListAsync(); // LC043
foreach (var user in users)
{
Send(user);
}
await foreach (var user in stream)
{
Send(user);
}
SaveChangesAsync inside a loop
foreach (var order in orders)
{
order.Status = OrderStatus.Sent;
await db.SaveChangesAsync(cancellationToken); // LC010
}
foreach (var order in orders)
{
order.Status = OrderStatus.Sent;
}
await db.SaveChangesAsync(cancellationToken);
Safer Async Patterns
- Use EF Core’s async counterpart inside async code:
ToListAsync,CountAsync,FirstOrDefaultAsync,SaveChangesAsync,FindAsync,ExecuteUpdateAsync, andExecuteDeleteAsync. - Pass the available
CancellationTokenthrough async query and save APIs. - Do not wrap synchronous EF Core database calls in
Task.Runto make them look async. - Use
await foreachwhen anIAsyncEnumerable<T>only needs one pass. - Batch entity changes and call
SaveChangesAsynconce unless each item has a deliberate commit boundary.
Review Checklist
- Does every async request, worker, or handler path avoid synchronous EF Core database APIs?
- Do
ToListAsync,CountAsync,FirstOrDefaultAsync,SaveChangesAsync, and similar calls receive the available token? - Are async streams processed with
await foreachinstead of buffered into memory for one loop? - Are write loops batching changes before a single
SaveChangesAsynccall? - Are deliberate exceptions documented with narrow suppressions instead of broad analyzer disablement?
CI Severity Starter
[*.cs]
# Async EF Core execution
dotnet_diagnostic.LC008.severity = warning
dotnet_diagnostic.LC026.severity = suggestion
dotnet_diagnostic.LC043.severity = suggestion
dotnet_diagnostic.LC010.severity = warning
Use the EF Core query analyzer CI guide to promote selected async rules from local warnings to pull-request checks.
Related Guides
- EF Core query performance checklist
- EF Core analyzer rules
- EF Core CancellationToken analyzer
- EF Core SaveChanges in loop analyzer
- EF Core DbContext lifetime analyzer
- EF Core query analyzer for CI
Official Links
- Canonical repository: github.com/georgepwall1991/LinqContraband
- Official NuGet package: nuget.org/packages/LinqContraband
- Documentation hub: georgepwall1991.github.io/LinqContraband