Skip to content

Repository files navigation

LinqContraband

LinqContraband icon — EF Core LINQ performance Roslyn analyzer

Stop Smuggling Bad Queries into Production

NuGet Downloads License: MIT Build Coverage OpenSSF Scorecard

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/AsEnumerable before 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 / ExecuteSqlRaw interpolation)
  • 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.

Try it on your code first

Scan a solution without changing it (.NET 10 SDK):

dnx LinqContraband.Scan

The scanner builds the solution in the current directory with the analyzers injected. It then prints which rules fired, how often, and in which files, and writes a SARIF file you can upload to GitHub code scanning. On .NET 8 or 9, install it once with dotnet tool install -g LinqContraband.Scan and run linqcontraband-scan.

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 ships as the LinqContraband analyzer package and the LinqContraband.Scan .NET tool, both on NuGet. It is not distributed as a standalone ZIP installer or executable; treat third-party ZIP downloads as untrusted.

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)

LinqContraband Roslyn analyzer warnings for EF Core LINQ performance — LC001 client-side evaluation, LC002 premature materialization, LC007 N+1, LC018 FromSqlRaw SQL injection

2. Before / after code fix (premature materialization)

Before and after: EF Core ToList before Where fixed to filter then materialize with LC002 premature materialization code fix

3. Product loop — analyzer in IDE and CI

LinqContraband product loop: Roslyn analyzer build diagnostics for DbContext and IQueryable in the IDE and ContinuousIntegrationBuild CI

30-second path

  1. Reference the package with PrivateAssets="all".
  2. Keep writing EF Core LINQ as usual (DbSet, IQueryable, Include, SaveChanges).
  3. Build as usual, in the IDE or with dotnet build. The analyzers run as part of the compiler.
  4. Fix any LC00x warnings (many have code fixes).
  5. 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 / DbContext APIs
  • 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:

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 (defaults: 4 and 3)
dotnet_code_quality.LC038.include_threshold = 6
dotnet_code_quality.LC042.query_operator_threshold = 5

Advisory rules default to Info, so they show up as IDE hints without drowning out the higher-confidence warnings. dotnet build does not print Info diagnostics; to see them on the command line, use the strict preset (it raises them to warnings) or write a SARIF log with -p:ErrorLog=linqcontraband.sarif.

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

About

EF Core LINQ performance analyzer and Roslyn analyzer for catching N+1 queries, client-side evaluation, sync-over-async, raw SQL risks, and other query issues at compile time.

Topics

Resources

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages