Why Project Instead of Returning Full Entities
Projection means reshaping a query's result using Select() so that only the specific fields you need are returned, rather than materializing full entity objects with every column and every tracked navigation property. Instead of context.Products.ToListAsync() returning entire Product entities, context.Products.Select(p => new ProductSummaryDto { Id = p.Id, Name = p.Name, Price = p.Price }).ToListAsync() generates SQL that only selects the Id, Name, and Price columns, reducing both the data transferred from the database and the memory allocated on the client.
Cricket analogy: It is like a scoreboard operator displaying only runs, wickets, and overs instead of the entire player database, showing exactly what the crowd needs rather than every stat on file.
Projecting Anonymous Types and DTOs
You can project into an anonymous type for quick, local use within a method, or into a named DTO class when the shape needs to cross a method or API boundary. Because projected results are not entity types by default, EF Core does not attach change tracking to them, which means they consume less memory and cannot accidentally be saved back via SaveChangesAsync() unless you explicitly track them, making projections a natural fit for read-only scenarios like listing pages and API responses.
Cricket analogy: It is like a quick scribbled note of 'need 6 off 2' during a chase, useful in the moment but never entered into the official scorebook, versus a formal scorecard entry that becomes part of the permanent record.
public record ProductSummaryDto(int Id, string Name, decimal Price, string CategoryName);
var summaries = await context.Products
.Where(p => p.IsActive)
.Select(p => new ProductSummaryDto(
p.Id,
p.Name,
p.Price,
p.Category.Name)) // navigation flattened directly in the projection
.ToListAsync();
// SQL generated: SELECT p.Id, p.Name, p.Price, c.Name
// FROM Products p JOIN Categories c ON p.CategoryId = c.Id WHERE p.IsActive = 1Flattening Related Data Without N+1
One of the strongest reasons to use Select() is that it can pull fields from related entities directly into a flat DTO in a single query, as shown above with p.Category.Name, without requiring Include() at all. EF Core translates the navigation access into a JOIN automatically because it appears inside the projection. This avoids both the overhead of loading full related entities and the N+1 risk that lazy loading would introduce if you accessed p.Category.Name outside of a projection.
Cricket analogy: It is like a stats graphic pulling a bowler's team name directly into a single combined caption without loading that team's entire squad roster and history separately.
Combining Select() with AsNoTracking() is a common performance pattern for read-only endpoints: AsNoTracking() disables EF Core's change tracker snapshot comparison, and Select() limits column retrieval, together minimizing both CPU and network overhead.
Projections that reference navigation collections, like p.Reviews.Select(r => r.Rating), can still generate correlated subqueries or multiple result sets. Always inspect generated SQL with ToQueryString() when projecting nested collections to confirm EF Core did not fall back to client evaluation.
- Select() reshapes query results to only the columns needed, reducing data transfer and memory usage.
- Projected results are not change-tracked by default, making them well suited to read-only scenarios.
- Projections can flatten related entity data directly into a DTO without calling Include().
- EF Core automatically generates JOINs when a navigation property is accessed inside a Select projection.
- Combining Select() with AsNoTracking() is a common pattern for high-performance read endpoints.
- Named DTO records or classes are preferable to anonymous types when the shape crosses a method boundary.
- Projecting nested collections can still generate correlated subqueries; verify with ToQueryString().
Practice what you learned
1. What is the primary benefit of using Select() to project query results into a DTO?
2. Are results from a Select() projection into a DTO tracked by EF Core's change tracker by default?
3. How does EF Core handle a navigation property accessed inside a Select projection, like p.Category.Name?
4. Which combination is a common high-performance pattern for read-only list endpoints?
Was this page helpful?
You May Also Like
LINQ to Entities
How EF Core translates LINQ query syntax into SQL, and the rules that govern what can and cannot be pushed down to the database.
Filtering, Sorting, and Paging
Building efficient, parameterized queries with Where, OrderBy, and Skip/Take, plus the tradeoffs of offset versus keyset pagination.
Eager, Lazy, and Explicit Loading
The three strategies EF Core offers for loading related data, and how choosing the wrong one causes the N+1 query problem.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics