LinqContraband 5.10.0
See the version list below for details.
dotnet add package LinqContraband --version 5.10.0
NuGet\Install-Package LinqContraband -Version 5.10.0
<PackageReference Include="LinqContraband" Version="5.10.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="LinqContraband" Version="5.10.0" />
<PackageReference Include="LinqContraband"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add LinqContraband --version 5.10.0
#r "nuget: LinqContraband, 5.10.0"
#:package LinqContraband@5.10.0
#addin nuget:?package=LinqContraband&version=5.10.0
#tool nuget:?package=LinqContraband&version=5.10.0
LinqContraband
<div align="center">
![]()
Stop Smuggling Bad Queries into Production
</div>
Compile-time EF Core LINQ performance analyzer for .NET — a high-signal Roslyn analyzer that catches N+1 queries, client-side evaluation, premature materialization, sync-over-async, missing AsNoTracking, raw SQL injection risks, and other DbContext query issues in the editor and CI—not production.
The problem
Entity Framework Core and LINQ compile cleanly even when the query shape will hurt production. An N+1 Find inside a loop, ToList() before Where, a local method that forces client-side evaluation, sync-over-async on DbContext, or FromSqlRaw($"...") only fail under load, at 3 AM, or as a security incident.
Runtime profilers and code review miss what static analysis can prove from your IQueryable chains and EF Core API usage.
What it catches
LinqContraband reports proven EF Core LINQ and DbContext pitfalls early:
- N+1 database execution inside loops (
Find, materializers, explicit load) - premature materialization (
ToList/AsEnumerablebefore filters) - client-side evaluation risk from non-translatable local methods
- sync-over-async EF Core calls in async methods
- missing or misused AsNoTracking (tracking tax, silent writes, mixed modes)
- Cartesian explosion and missing/excessive Include paths
- SaveChanges inside loops and nested SaveChanges
- raw SQL injection patterns (
FromSqlRaw/ExecuteSqlRawinterpolation) - DbContext lifetime and concurrent same-context operations
- unbounded materialization, projection waste, and pagination without OrderBy
When the analyzer cannot prove an EF-backed query shape statically, it stays quiet. High-signal feedback, not noisy guesses.
Install
dotnet add package LinqContraband
That adds the latest release. To edit the project file by hand instead, use the version shown on the NuGet badge:
<PackageReference Include="LinqContraband" Version="x.y.z" PrivateAssets="all" />
No runtime dependency is added to your app. LinqContraband runs as a Roslyn analyzer during build and in supported IDEs (Visual Studio, Rider, VS Code / C# Dev Kit) and CI.
Install only from NuGet or from this repository. LinqContraband is not distributed as a standalone ZIP installer or executable; treat third-party ZIP downloads as untrusted.
- Official package: nuget.org/packages/LinqContraband
- Canonical source: github.com/georgepwall1991/LinqContraband
- Documentation: georgepwall1991.github.io/LinqContraband
A taste
// LC002: ToList() pulls every order into memory, then filters in C#.
var slow = db.Orders.ToList().Where(o => o.DueDate < today);
// Fix (offered as a code fix): filter in SQL, then materialize.
var fast = db.Orders.Where(o => o.DueDate < today).ToList();
// LC007: one database round trip per customer (N+1).
foreach (var id in customerIds)
customers.Add(db.Customers.Find(id));
// Fix: one query for the whole set.
customers = db.Customers.Where(c => customerIds.Contains(c.Id)).ToList();
Every diagnostic's help link in your IDE opens the rule's page, which shows the problem, the fix, and the cases the rule deliberately leaves alone.
See it work
Product-flow diagrams from real sample diagnostics and shipped LC message formats:
1. Build / IDE diagnostics (EF Core LINQ)
2. Before / after code fix (premature materialization)
3. Product loop — analyzer in IDE and CI
30-second path
- Reference the package with
PrivateAssets="all". - Keep writing EF Core LINQ as usual (
DbSet,IQueryable,Include,SaveChanges). - Build in the IDE or with
ContinuousIntegrationBuild=trueon the command line so analyzers run. - Fix any
LC00xwarnings (many have code fixes). - Optionally promote critical rules to error in
.editorconfig(see Configuration below).
Feature snapshot
| Area | What LinqContraband does |
|---|---|
| N+1 queries | Flags database execution inside loops and SaveChanges-in-loop write amplification. |
| Materialization | Catches premature ToList/AsEnumerable and redundant second materializers. |
| Translation | Reports local methods and non-translatable string/date patterns that risk client-side evaluation. |
| Tracking | Guides AsNoTracking, silent-write, and mixed tracking-mode hazards. |
| Loading | Detects Cartesian explosion, missing Include, deep ThenInclude, excessive eager loading. |
| Async | Sync-over-async, missing CancellationToken, async stream buffering, concurrent DbContext use. |
| Raw SQL | Interpolated FromSqlRaw/ExecuteSqlRaw and constructed SQL string risks. |
| Modeling | Missing primary keys and explicit foreign-key properties when statically provable. |
Compatibility
- .NET / Roslyn hosts: Visual Studio, Rider, VS Code (C# Dev Kit), and
dotnet build/ CI - EF Core: Modern Entity Framework Core versions used with C#
IQueryable/DbContextAPIs - Package kind: Development dependency analyzer (
PrivateAssets="all"); no app runtime package
Rules
56 rules, 39 with automatic code fixes. Each rule links to its full page: what it flags, why it matters, how to fix it, and where it deliberately stays quiet.
| Rule | What it catches | Default severity | Code fix |
|---|---|---|---|
| LC001 | Client-side evaluation risk: Local method usage in IQueryable | Warning | Yes |
| LC002 | Premature query continuation after materialization | Warning | Yes |
| LC003 | Prefer Any() over Count() existence checks | Warning | Yes |
| LC004 | Deferred Execution Leak: IQueryable passed as IEnumerable | Warning | Yes |
| LC005 | Multiple OrderBy calls | Warning | Yes |
| LC006 | Cartesian Explosion Risk: Multiple Collection Includes | Warning | Yes |
| LC007 | N+1 Problem: Database execution inside loop | Warning | Yes |
| LC008 | Sync-over-Async: Synchronous EF Core method in Async context | Warning | Yes |
| LC009 | Performance: Missing AsNoTracking() in Read-Only path | Info | Yes |
| LC010 | N+1 Write Problem: SaveChanges inside loop | Warning | Yes |
| LC011 | Design: Entity missing Primary Key | Warning | Yes |
| LC012 | Optimize: Use ExecuteDelete() instead of RemoveRange() | Warning | Yes |
| LC013 | Disposed Context Query | Warning | Manual |
| LC014 | Avoid String.ToLower() or ToUpper() in LINQ queries | Warning | Manual |
| LC015 | Deterministic Pagination: OrderBy required before Skip/Take | Warning | Yes |
| LC016 | Avoid DateTime.Now/UtcNow in LINQ queries | Warning | Yes |
| LC017 | Performance: Consider using Select() projection | Info | Yes |
| LC018 | Avoid FromSqlRaw with interpolated strings | Warning | Yes |
| LC019 | Conditional Include Expression | Warning | Manual |
| LC020 | Avoid untranslatable string comparison overloads | Warning | Yes |
| LC021 | Avoid IgnoreQueryFilters | Warning | Yes |
| LC022 | Nested collection materialization inside projection | Info | Yes |
| LC023 | Use Find/FindAsync for primary key lookups | Info | Yes |
| LC024 | GroupBy with Non-Translatable Projection | Warning | Manual |
| LC025 | Avoid AsNoTracking with Update/Remove | Warning | Yes |
| LC026 | Missing CancellationToken in async call | Info | Yes |
| LC027 | Missing Explicit Foreign Key Property | Info | Yes |
| LC028 | Deep ThenInclude Chain | Warning | Manual |
| LC029 | Redundant identity Select | Info | Yes |
| LC030 | Potential DbContext lifetime mismatch | Info | Manual |
| LC031 | Unbounded Query Materialization | Info | Manual |
| LC032 | Use ExecuteUpdate for provable bulk scalar updates | Info | Yes |
| LC033 | Use FrozenSet for provably read-only membership caches | Info | Yes |
| LC034 | Avoid ExecuteSqlRaw with interpolated strings | Warning | Yes |
| LC035 | Missing Where before bulk execute | Info | Manual |
| LC036 | DbContext captured by thread work item | Warning | Manual |
| LC037 | Avoid constructed raw SQL strings | Warning | Manual |
| LC038 | Avoid excessive eager loading | Info | Manual |
| LC039 | Avoid repeated SaveChanges on the same context | Info | Manual |
| LC040 | Avoid mixing tracking modes on the same context | Info | Manual |
| LC041 | Single entity query over-fetches one consumed property | Info | Yes |
| LC042 | Complex query should be tagged | Info | Yes |
| LC043 | Prefer await foreach over buffering async streams | Info | Yes |
| LC044 | AsNoTracking query mutated then SaveChanges — silent data loss | Warning | Manual |
| LC045 | Missing Include: navigation accessed on materialized entity | Warning | Yes |
| LC046 | Concurrent EF Core operations on the same DbContext | Warning | Manual |
| LC047 | ExecuteDelete bypasses the tracked delete pipeline | Warning | Yes |
| LC048 | Tracked update can overwrite a concurrent change | Warning | Manual |
| LC049 | Include is ignored by a Select projection | Info | Yes |
| LC050 | OrderBy before Distinct is discarded | Warning | Yes |
| LC051 | ToAsyncEnumerable() runs an EF Core query synchronously | Warning | Yes |
| LC052 | Model data uses a value that changes on every run | Warning | Manual |
| LC053 | Global query filter silently replaced by another HasQueryFilter | Warning | Yes |
| LC054 | Migrate called inside a user transaction | Warning | Yes |
| LC055 | OnModelCreating override skips the base configuration | Warning | Yes |
| LC056 | LINQ composed over a stored procedure call | Warning | Yes |
Browse the same rules grouped by failure mode in the rule catalog, or start from a topic guide:
- N+1 query detector and SaveChanges in loops
- Client-side evaluation and premature materialization
- Include and eager loading and projection
- AsNoTracking and change tracking and DbContext lifetime
- Async queries and CancellationToken
- Raw SQL injection, ExecuteUpdate, and pagination OrderBy
- EF Core query performance checklist
Configuration
Pick a preset with one line in your project file (or Directory.Build.props):
<PropertyGroup>
<LinqContrabandPreset>security</LinqContrabandPreset>
</PropertyGroup>
| Preset | What it does |
|---|---|
security |
SQL injection rules (LC018, LC034, LC037, plus EF Core's own EF1002 and EF1003, which LC018 and LC034 defer to) fail the build. |
critical |
security plus the runtime-failure and silent data-loss rules (LC013, LC019, LC036, LC044, LC046, LC047, LC048, LC054, LC055, LC056) fail the build. |
strict |
Every warning rule fails the build and every advisory rule becomes a warning. |
essentials |
Advisory (Info) rules are turned off; warning rules keep their defaults. |
Combine presets with ;, for example security;essentials. Your own .editorconfig entries still win over a preset.
Or set any rule's severity yourself in .editorconfig:
[*.cs]
dotnet_diagnostic.LC001.severity = error
dotnet_diagnostic.LC002.severity = error
dotnet_diagnostic.LC003.severity = warning
# Optional rule-specific thresholds
dotnet_code_quality.LC038.include_threshold = 4
dotnet_code_quality.LC042.query_operator_threshold = 3
Advisory rules default to Info, so they show up as hints without drowning out the higher-confidence warnings.
LinqContraband also hides the .NET SDK's culture and string-comparison warnings (CA1862, CA1304, CA1305, CA1307, CA1309, CA1310, CA1311) inside EF Core query lambdas, where their suggested StringComparison or CultureInfo overloads cannot be translated to SQL and would make the query throw. In-memory LINQ keeps them. To keep one inside queries as well, add its suppression id (LCS plus the CA number, such as LCS1862) to <NoWarn>; see LC014.
Run it in CI
LinqContraband runs inside the normal dotnet build, so a CI job needs no database, service container, or extra tool:
- run: dotnet restore
- run: dotnet build --configuration Release --no-restore
Use the security or critical preset (see Configuration) to block pull requests on the rules that matter most, or
promote individual rules to error in .editorconfig. The
CI guide covers a gradual rollout.
Contributing
Found a new way to smuggle bad queries? Open an issue or send a pull request. The contributing guide explains how to add or change a rule.
License: MIT
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.15.0 | 0 | 9/25/2026 |
| 5.14.0 | 0 | 9/25/2026 |
| 5.13.0 | 106 | 9/25/2026 |
| 5.12.0 | 41 | 9/24/2026 |
| 5.11.0 | 42 | 9/24/2026 |
| 5.10.0 | 170 | 9/23/2026 |
| 5.8.1 | 98 | 9/22/2026 |
| 5.8.0 | 1,522 | 9/10/2026 |
| 5.7.65 | 9,633 | 8/17/2026 |
| 5.7.64 | 116 | 8/17/2026 |
| 5.7.63 | 107 | 8/17/2026 |
| 5.7.62 | 114 | 8/17/2026 |
| 5.7.61 | 111 | 8/16/2026 |
| 5.7.60 | 111 | 8/16/2026 |
| 5.7.59 | 122 | 8/13/2026 |
| 5.7.58 | 111 | 8/10/2026 |
| 5.7.57 | 113 | 8/10/2026 |
| 5.7.56 | 113 | 8/10/2026 |
| 5.7.55 | 117 | 8/10/2026 |
| 5.7.54 | 110 | 8/10/2026 |
Six new rules, five with code fixes: LC051 reports ToAsyncEnumerable() on an EF Core query, which runs it synchronously (fix: AsAsyncEnumerable()); LC052 reports DateTime.Now, Guid.NewGuid() and similar values in HasData seed data or HasDefaultValue, which make every migration differ; LC053 reports a second unnamed HasQueryFilter that silently replaces a tenant or soft-delete filter (fix: merge with &&); LC054 reports Migrate() inside a user transaction, which EF Core 9+ rejects; LC055 reports an OnModelCreating override that skips base.OnModelCreating; LC056 reports LINQ composed over a stored procedure call in FromSql. LC042 now scores query shape instead of counting method names and gains a TagWith/TagWithCallSite fix. A new suppressor hides the .NET SDK's CA1862, CA1304, CA1305, CA1307, CA1309, CA1310 and CA1311 warnings inside EF Core query lambdas, where their suggested overloads cannot be translated to SQL. Fewer false positives: LC001 trusts LINQKit, NeinLinq, DelegateDecompiler and HasDbFunction mappings; LC001, LC004, LC016, LC020 and LC022 skip in-memory AsQueryable() queries; LC007 skips batch, polling and retry loops; LC017 treats entity writes as escapes; LC026 accepts CancellationToken.None; LC031 stops at project query helpers; LC045 follows ApplyConfigurationsFromAssembly; LC018 and LC034 defer to EF Core's EF1002/EF1003 (LC037 is unchanged). Safer fixes: LC009 withholds AsNoTracking() when entities leave the method, and LC018 rewrites to FromSql. The documentation site now covers LC048, LC049 and LC050 in its guides.