Summary#
Select says which columns you want, and in what shape:
class OrderRow {
public string Code;
public decimal Total;
}
entity Order {
[Required, MaxLength(40)] string Code;
[MaxLength(60)] string Region;
decimal Total;
bool Cancelled;
}
List<string> Codes() {
return Order.Select(o => o.Code).ToList(); // one column → a List<string>
}
List<OrderRow> Rows() {
return Order.Where(o => !o.Cancelled)
.Select(o => new OrderRow { Code = o.Code, Total = o.Total })
.ToList(); // → a class you declared
}The projection becomes the SQL SELECT list, so the columns you did not name are never read — no wide rows over
the wire, and no entity to materialise.
Signature#
<Query>.Select(o => o.Column) // a scalar → List<string> / List<decimal> / …
<Query>.Select(o => o.Total * 2) // an expression scalar
<Query>.Select(o => new T { A = o.X, … }) // a `class` you declared → List<T>
<Query>.Select(o => new { o.X, o.Y }) // anonymous — usable on the spot, cannot be returnedDescription#
It must be the last clause#
A Select ends the chain. The compiler enforces a narrow window around it, and the restrictions are not
arbitrary — each one is a thing SQL cannot do once the rows have been reshaped:
Before a Select, only: Where · OrderBy / OrderByDescending · Take.
ThenBymay not precede aSelect. Sort the projected result instead.Skipmay not precede aSelect(thoughTakemay). To page a projection: project into aclassand page that, or page the entity query and project after.Includemay not precede aSelect— and it would be meaningless if it could:Includepre-loads related entity rows, and a projection does not return entity rows at all. Just select what you want.
After a Select, only: ToList() (a no-op — the projection already materialises) and Distinct().
There are no terminals over a projection: no First(), no Count(), no Sum(). Do the aggregate over the entity
query instead (Sum / Average / Min / Max / Count), or group it (GroupBy (and HAVING)).
The one exception: Distinct().Count()#
The one composition allowed after a projection, because it is a single SQL expression — and it answers a question you genuinely cannot get another way:
int RegionsSoldInto() {
return Order.Select(o => o.Region).Distinct().Count(); // → COUNT(DISTINCT region)
}Distinct() over a scalar projection then Count() becomes COUNT(DISTINCT col). It must be the last clause, and
the projection must be a scalar. See Distinct.
Which shape to reach for#
- A scalar (
o => o.Code) when you want a list of values — ids to pass on, codes to render, amounts to sum in code. You get a realList<T>. - A
classwhen you want rows with names, especially across a function boundary. This is the workhorse: declare the shape, project into it, return it. It is a plain data shape (class methods) — no entity, no tracking, no lazy loading, nothing to surprise you later. A class projection can also back a reactivelive varin a component — a live list of the shape you render, refreshing on commit (The reactivity & lifecycle model). - Anonymous (
o => new { o.Code, o.Total }) only where the result is consumed on the spot. It has no name, so it cannot be a return type.
It does not change what security allows#
A projection narrows the columns, never the rows. The read rules are compiled into
the same statement, so Select cannot be used to see a row you were not granted — and a
field mask (deny read PasswordHash when …) still applies to the column you projected.
Projecting is a performance and shape decision, not an access one.
Examples#
Projecting a computed value, and a narrow row for a list view — the common case, and the one that keeps a grid fast:
class OrderCard {
public string Code;
public decimal Total;
public decimal WithVat;
public bool Big;
}
List<OrderCard> Cards(decimal bigFrom) {
return Order.Where(o => !o.Cancelled)
.OrderByDescending(o => o.Total)
.Take(50)
.Select(o => new OrderCard {
Code = o.Code,
Total = o.Total,
WithVat = o.Total * 1.2m, // computed in the database
Big = o.Total > bigFrom }) // a captured local works, like any parameter
.ToList();
}Where did my row come in the order?#
Select((s, i) => …) — C#'s index-aware projection — works over stored rows once the chain names an order: i is
the row's 0-based position in that order, computed by the database. The partitioned forms (per-group ranks, the
previous row's value) are the window functions.
See also#
- GroupBy (and HAVING) — a projection with a
GroupByin front of it: one row per group - Distinct —
Distinct(), andDistinct().Count() - Include (pre-loading relations) — the opposite need: keep the entity rows, but pre-load their relations
- class methods — the
classa projection targets - Querying data — where a projection sits in the chain