Documentation
EF Core ExecuteUpdate Analyzer
Use LinqContraband as an EF Core ExecuteUpdate analyzer for set-based updates, ExecuteDelete replacements, and missing Where filters before bulk writes.
EF Core ExecuteUpdate Analyzer
LinqContraband is an EF Core ExecuteUpdate analyzer for .NET projects that want bulk write opportunities and bulk
write safety issues to appear during development and CI. It helps reviewers spot slow tracked update loops, expensive
RemoveRange deletes, and unfiltered ExecuteUpdate or ExecuteDelete calls before they reach production data.
Install the official analyzer package:
dotnet add package LinqContraband
Why Bulk Writes Need Review
EF Core’s set-based APIs can be dramatically faster than loading every row, changing tracked entities one by one, and
calling SaveChanges. The trade-off is that ExecuteUpdate and ExecuteDelete run immediately in the database and
bypass EF Core change tracking.
That makes two review questions important:
- Can this tracked loop or
RemoveRangecall safely become a set-based SQL operation? - Is every destructive bulk operation filtered before it touches rows?
The slow shape looks like this:
foreach (var user in db.Users.Where(user => user.IsInactive))
{
user.Status = "Archived";
}
db.SaveChanges();
The dangerous shape is the opposite problem:
db.Users.ExecuteDelete();
The first query may waste time and memory. The second can affect the whole table.
If the immediate problem is repeated SaveChanges calls rather than a set-based update opportunity, use the
EF Core SaveChanges in loop analyzer guide.
LinqContraband Rules That Help
| Rule | What it detects | Review direction |
|---|---|---|
| LC032: use ExecuteUpdate for bulk updates | A provable tracked update loop followed by SaveChanges where a uniform scalar update can become ExecuteUpdate. |
Use ExecuteUpdate or ExecuteUpdateAsync when bypassing tracking, callbacks, interceptors, and deferred-save timing is acceptable. |
| LC012: use ExecuteDelete instead of RemoveRange | RemoveRange(query) where the argument is still query-shaped and no relevant later SaveChanges would make the rewrite behaviour-changing. |
Use ExecuteDelete or ExecuteDeleteAsync after confirming cascades, interceptors, and unit-of-work timing are not required. |
| LC035: missing Where before ExecuteDelete or ExecuteUpdate | EF Core bulk execute calls on a query with no proven Where filter. |
Add the tenant, lifecycle, account, or business filter before the bulk write. |
Safer Bulk Write Patterns
Use ExecuteUpdate for a uniform scalar change:
await db.Users
.Where(user => user.IsInactive)
.ExecuteUpdateAsync(setters => setters
.SetProperty(user => user.Status, user => "Archived"));
Use ExecuteDelete for filtered deletes that do not need tracked entity callbacks:
await db.Logs
.Where(log => log.CreatedAt < cutoff)
.ExecuteDeleteAsync();
Keep the normal tracked workflow when entity callbacks, interceptors, optimistic concurrency handling, or deferred
SaveChanges timing are part of the business rule:
var users = await db.Users
.Where(user => user.RequiresDomainEvent)
.ToListAsync();
foreach (var user in users)
{
user.ArchiveWithDomainEvent(clock.UtcNow);
}
await db.SaveChangesAsync();
Bulk Write Safety Checklist
- Is the target set filtered by tenant, account, lifecycle state, date range, or another business boundary?
- Would bypassing change tracking skip required domain events, entity callbacks, save interceptors, or client-side cascades?
- Does the code rely on the affected-row count returned from
SaveChanges? - Does the update value read a property changed earlier in the same loop iteration?
- Should the operation be wrapped in an explicit transaction, audit record, or reviewed suppression?
CI Severity Starter
Start with filter safety as a warning, then promote it once the team agrees on bulk-write policy:
[*.cs]
# Set-based write opportunities
dotnet_diagnostic.LC012.severity = suggestion
dotnet_diagnostic.LC032.severity = suggestion
# Bulk write safety
dotnet_diagnostic.LC035.severity = warning
Use the EF Core query analyzer CI guide when these diagnostics should run on every pull request. Use the EF Core query performance checklist when reviewers need the broader query-performance context.
Official Links
- Canonical repository: github.com/georgepwall1991/LinqContraband
- Official NuGet package: nuget.org/packages/LinqContraband
- Full rule catalog: georgepwall1991.github.io/LinqContraband/rule-catalog.html
- Safe install guidance: Official LinqContraband downloads and authenticity