.Select<T>(fields)
Projects only the specified fields into a new instance of T. Supports direct properties, whole navigation objects/collections, and nested navigation paths — including paths that traverse collection properties.
Signature
public static IQueryable<T> Select<T>(this IQueryable<T> query, List<string> fields)
where T : class| Parameter | Type | Description |
|---|---|---|
query | IQueryable<T> | Source query to project |
fields | List<string> | Property paths to include |
Projection rules
| Path style | Behaviour | Example input | Effect on result |
|---|---|---|---|
| Direct scalar | Bound directly | "Name" | Name: "Laptop" |
| Whole navigation object (non-dotted) | Bound as-is | "Category" | Category: { Id: 5, Name: "Electronics", … } |
| Whole navigation collection (non-dotted) | Bound as-is | "Brands" | Brands: [{ Id: 1, … }, …] |
| Dotted through reference navigation | Recursively projected | "Category.Name" | Category: { Id: …, Name: "Electronics" } |
| Dotted through collection navigation | Per-element Select().ToList() | "Category.Vendors.Id" | Category: { Vendors: [{ Id: 1 }, …] } |
| Multi-level (reference + collection) | Nested recursively | "Category.Vendors.Product.Name" | Category: { Vendors: [{ Product: { Name: "…" } }] } |
| Dotted through an application's own struct (3.4.0) | Built member by member | "Name.Ar" | Name: { Ar: "…", En: null } |
| Dotted through a collection of structs (3.4.0) | Per-element Select().ToList(), or ToArray() for an array | "Pairs.Shown" | Pairs: [{ Shown: "…", Hidden: null }, …] |
| Dotted beneath a value type the framework declares | Left unbound | "CreatedAt.Year" | CreatedAt keeps its default |
Id property is automatically included alongside any requested sub-fields — but only when the nested type actually declares an Id.Structs (3.4.0)
An application's own struct — a value type outside the System namespaces — is built with exactly what was named, and no Id is added: Name.En keeps its default when only Name.Ar is named. Nested structs are built level by level, a collection inside a struct is carried, and the struct is read with a plain member access, so it works on EF Core and in memory. Until 3.4.0 every value-typed member a path went beneath was skipped, so Select(["Name.Ar"]) returned the struct's default, an empty name.
- A nullable struct is named through
Value:"Alias.Value.Ar"builds it where it has a value and leaves it null where it has none."Alias.Value"whole, or"Alias.HasValue"alone, leaves it unbound. - A collection of structs is built element by element, into a
List<T>, any member type a list can be assigned to, or aT[]. A null collection stays null, and a collection of scalars is still bound whole. Until 3.4.0 the collection was bound whole, so each element came back with every member. - Left unbound: a class navigation inside a struct (its siblings are built), a struct or an element in which nothing named can be set, a collection of nullable structs, and a path beneath a framework value type such as
"CreatedAt.Year"— a typed row cannot hold the year apart from the date.SelectDynamiccarries such a path as it is named. - On EF Core, a list of structs the source projection builds with a collection initializer,
new List<Pair> { new Pair { … } }, cannot be selected from again: a path beneath it fails withInvalidOperationException("could not be translated"), as it always did forSelectDynamic. A list the projection reads from a query is built as named.
Validations
queryandfieldscannot be null.fieldsmust have at least one entry.- Every field must exist on
T(case-insensitive, auto-normalized). Tmust have a parameterless constructor — checked at run time, not by a type constraint —SelectTypeMustHaveParameterlessConstructor.
.Select<T>(fields) requires T to have a parameterless (default) constructor. If T does not have one, a LogicException is thrown whose Message is the stable code SelectTypeMustHaveParameterlessConstructor and whose Subject is T's name. Before 3.1.0 the message was an English sentence with the type name inside it — see breaking changes. Most EF Core entity classes have parameterless constructors by default. If your type does not, use .SelectDynamic<T> instead.Returns
IQueryable<T> — a projected query. New instances of T are constructed via the parameterless constructor and member-bound from the requested fields.
Examples
Direct scalars.
var projected = dbContext.Products
.Select(new List<string> { "Id", "Name", "Price" });Dotted path through a reference navigation.
var projected = dbContext.Products
.Select(new List<string> { "Id", "Name", "Category.Name" });Category is projected with only the requested Name sub-field (Id auto-included).
Dotted path through a collection navigation.
var projected = dbContext.Products
.Select(new List<string> { "Id", "Name", "Category.Vendors.Id" });Category.Vendors is a collection — each Vendor element is projected with only its Id (Id auto-included).
Whole navigation object (non-dotted).
var projected = dbContext.Products
.Select(new List<string> { "Id", "Name", "Category" });The entire Category object is bound as-is.
Whole collection (non-dotted).
var projected = dbContext.Products
.Select(new List<string> { "Id", "Name", "Brands" });The entire Brands collection is bound as-is.
JSON-driven projection.
{ "fields": ["Id", "Name", "Category.Name"] }See also
.SelectDynamic<T>— non-generic dynamic variant (no parameterless constructor needed)..Filter<T>— applywhere → order → page → selectin one call.- JSON Cookbook: Select.