Documentation
EF Core Projection Analyzer
Use LinqContraband as an EF Core projection analyzer for whole-entity loads, scalar projection opportunities, nested ToList in Select, and redundant identity Select calls.
EF Core Projection Analyzer
LinqContraband is an EF Core projection analyzer for .NET teams that want over-fetching, noisy Select calls, and
provider-sensitive projection shapes to show up during development and CI. It helps reviewers catch whole-entity loads
when code only needs a few columns, single-entity queries that only consume one scalar, nested ToList calls inside
projections, and redundant Select(x => x) chains.
Install the official analyzer package:
dotnet add package LinqContraband
Why Projection Shape Matters
EF Core can translate a precise Select into SQL that reads only the columns a caller needs. Without projection, a
query can pull full rows, allocate unused entity properties, and track data that is only used for display or a simple
scalar result.
The expensive shape loads full entities and then reads one property:
var users = await db.Users
.Where(user => user.IsActive)
.ToListAsync(cancellationToken);
foreach (var user in users)
{
names.Add(user.DisplayName); // LC017
}
The safer shape projects the required data before materialization:
var names = await db.Users
.Where(user => user.IsActive)
.Select(user => user.DisplayName)
.ToListAsync(cancellationToken);
If the main issue is an early ToList or AsEnumerable boundary, use the
EF Core premature materialization analyzer guide.
LinqContraband Rules That Help
| Rule | What it detects | Review direction |
|---|---|---|
| LC017: whole entity projection | Large entity loads where later code uses only a small subset of properties. | Project a DTO, anonymous type, or scalar sequence with the fields the caller actually needs. |
| LC041: single entity scalar projection | First, Single, and related single-row queries where the local code consumes one scalar property. |
Project the scalar before materialization when the rewrite preserves no-row behaviour. |
| LC022: nested collection materialization inside projection | ToList, ToArray, or similar materializers inside projected collection members. |
Review whether the shape should stay provider-friendly, use split-query shaping, or intentionally return a concrete collection. |
| LC029: redundant identity Select | Select(x => x) or equivalent identity projections on queryable and enumerable chains. |
Remove the redundant projection while preserving any intentional boundary such as AsEnumerable. |
Common EF Core Projection Problems
Whole entity loaded for one or two fields
var products = await db.Products
.Where(product => product.Price > 100)
.ToListAsync(cancellationToken); // LC017
return products.Select(product => product.Name).ToList();
return await db.Products
.Where(product => product.Price > 100)
.Select(product => product.Name)
.ToListAsync(cancellationToken);
Single entity loaded for one scalar
var user = await db.Users
.FirstAsync(user => user.IsActive, cancellationToken); // LC041
return user.Email;
return await db.Users
.Where(user => user.IsActive)
.Select(user => user.Email)
.FirstAsync(cancellationToken);
Nested materializer inside Select
var customers = db.Customers
.Select(customer => new
{
customer.Id,
OrderIds = customer.Orders.Select(order => order.Id).ToList() // LC022
});
var customers = db.Customers
.Select(customer => new
{
customer.Id,
OrderIds = customer.Orders.Select(order => order.Id)
});
Identity projection noise
var users = await db.Users
.Where(user => user.IsActive)
.Select(user => user) // LC029
.ToListAsync(cancellationToken);
var users = await db.Users
.Where(user => user.IsActive)
.ToListAsync(cancellationToken);
Projection Review Checklist
- Does the caller need tracked entities, or would a DTO, anonymous type, scalar, or record be enough?
- Are large entities loaded when only one or two properties are used locally?
- Can a single-row query project the consumed scalar before
First,Single, or their async equivalents? - Is a nested collection materializer inside
Selectrequired by the DTO contract, or can the provider shape stay lazy? - Is
Select(x => x)just noise, or is the real intent a visible client-side boundary such asAsEnumerable?
CI Severity Starter
Start projection rules as suggestions while existing query shapes are cleaned up, then promote LC017 or LC041 where the team has a clear DTO/projection policy:
[*.cs]
# Projection and over-fetching
dotnet_diagnostic.LC017.severity = suggestion
dotnet_diagnostic.LC041.severity = suggestion
dotnet_diagnostic.LC022.severity = suggestion
dotnet_diagnostic.LC029.severity = suggestion
Use the EF Core query analyzer CI guide when projection diagnostics should appear on every pull request. Use the EF Core query performance checklist when reviewers need the broader query-performance context.
Related Guides
- EF Core premature materialization analyzer
- EF Core query performance checklist
- EF Core analyzer rules
- EF Core async query analyzer
- EF Core AsNoTracking analyzer
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