100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Projections with Select

Using Select to shape query results into DTOs, reducing data transfer and avoiding unnecessary change tracking overhead.

QueryingIntermediate8 min readJul 10, 2026
Analogies

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.

csharp
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 = 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

Was this page helpful?

Topics covered

#EntityFrameworkCoreStudyNotes#MicrosoftTechnologies#ProjectionsWithSelect#Projections#Select#Project#Instead#StudyNotes#SkillVeris#ExamPrep