Analyzer rule
LC049: Include is ignored by a Select projection
LC049 flags EF Core Include calls that a later Select projection makes EF Core ignore, and removes them.
- Default severity
- Info
- Code fix
- Available
- Category
- Performance
- Domain
- Loading & Includes
Change the severity in .editorconfig:
[*.cs]
dotnet_diagnostic.LC049.severity = error
LC049: Include is ignored by a Select projection
In Plain Terms
You ask the kitchen to bring the whole meal with every side dish, then say “actually, just tell me the price”. The side dishes never leave the kitchen. The order slip still says “with sides”, though, and the next person to read it will believe they are coming.
Goal
Detect Include / ThenInclude on an EF Core query whose Select projects the root entity into scalars, DTOs, or anonymous types.
The Problem
EF Core applies Include only to entity instances the query returns. Once a Select turns Order into new { o.Id, o.Customer.Name } or new OrderDto { ... }, EF Core drops the Include without a warning. It loads nothing extra and costs nothing at runtime, but it is dead code that lies: reviewers read it as “customers are eager-loaded here”, and people copy it into queries where it does matter.
The projection already decides which related columns are read. o.Customer.Name inside the Select becomes a SQL join on its own.
// Violation: the Include is ignored because the query returns anonymous objects, not Orders.
var rows = await db.Orders
.Include(o => o.Customer)
.Include(o => o.Lines).ThenInclude(l => l.Product)
.Select(o => new
{
o.Id,
CustomerName = o.Customer.Name,
Skus = o.Lines.Select(l => l.Product.Sku).ToList()
})
.ToListAsync();
The Fix
Delete the Include and any ThenInclude calls chained to it. The query’s SQL does not change.
var rows = await db.Orders
.Select(o => new
{
o.Id,
CustomerName = o.Customer.Name,
Skus = o.Lines.Select(l => l.Product.Sku).ToList()
})
.ToListAsync();
If you meant to load full entities, remove the Select instead and keep the Include.
Analyzer Logic
ID: LC049
Category: Performance
Severity: Info
- Start at a
Queryable.Selectwhose selector is a lambda over an entity-like element type. - Walk back through the fluent chain while the operators keep returning the same entities:
Where,OrderBy/ThenBy(and descending),Skip,Take,Distinct,Reverse,ThenInclude,AsNoTracking,AsNoTrackingWithIdentityResolution,AsTracking,TagWith,TagWithCallSite,IgnoreQueryFilters,IgnoreAutoIncludes,AsSplitQuery, andAsSingleQuery. Any other operator stops the walk. - Collect every EF Core
Include(lambda, filtered-lambda, and string overloads fromMicrosoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions) on that element type. - Report each one, unless the projection could still return an entity. An entity value (the lambda parameter, a navigation reached from it, or a LINQ operator that passes entities through) is allowed only as:
- the instance of a member access (
o.Customer.Name), - one side of a
nullcomparison (o.Customer != null ? o.Customer.Name : null), - the source of a LINQ operator whose own result is checked in turn (
o.Lines.Count(),o.Lines.Select(l => new LineDto { Sku = l.Sku })).
- the instance of a member access (
Anything else keeps the rule quiet. Objects the projection constructs itself, such as DTOs and anonymous types, are not entity values even when their type is a user class.
When it stays quiet (non-goals)
Select(o => o),Select(o => new { Order = o }), or any projection that returns the root entity somewhere in its shape.- Projections that return a navigation entity or collection, such as
Select(o => o.Customer),new { o.Lines }, oro.Lines.Select(l => l.Product). EF Core carries the matchingThenIncludeonto those entities, so the Include can still matter. - Entities passed to a method,
Select(o => Map(o)), because the helper may return the entity or read its navigations client-side. AsEnumerable()or any other client-side boundary before theSelect, because the entities are materialized with their includes first.GroupBy,Join,SelectMany, or other shape-changing operators between theIncludeand theSelect.- Includes stored in a local and projected later (
var q = db.Orders.Include(...); q.Select(...)). Only fluent chains are analyzed. - Lookalike
Includemethods outsideMicrosoft.EntityFrameworkCore.
Code Fix
The fixer removes the reported Include together with the ThenInclude calls chained to it, and keeps every other operator in place. Fix All removes every ignored Include in a chain in one pass. The static form EntityFrameworkQueryableExtensions.Include(source, ...) is reported without a fix.
Test Cases
Violations
db.Orders.Include(o => o.Customer).Select(o => o.Id);
db.Orders.Include("Lines").Select(o => new { o.Id, Count = o.Lines.Count() });
db.Orders.AsNoTracking().Include(o => o.Customer).Select(o => new OrderRow(o.Id, o.Customer.Name));
Valid
db.Orders.Include(o => o.Customer).ToList();
db.Orders.Include(o => o.Customer).Select(o => o.Customer);
db.Orders.Include(o => o.Customer).AsEnumerable().Select(o => o.Customer.Name);