Summary#
Rows the client already has sort in memory, and the key is a selector:
rows.OrderBy(x => x.Title) // a key lambda
rows.OrderBy(column.Value) // the selector passed directly — C#'s method-group formThere is no string form. OrderBy("Title") does not exist here for the same reason a column's value is not named by
a string: a rename compiles and fails at runtime, and the compiler cannot check what it cannot see.
Signature#
rows.OrderBy(<selector>) // ascending
rows.OrderByDescending(<selector>) // descending
// <selector> is either:
x => x.Field // a lambda taking ONE parameter — the row
column.Value // a Func<row, key> valueThe receiver is a sequence the client holds: a component's T[] parameter, or a List<T>. A .OrderBy on a
query member is a different thing — see OrderBy / ThenBy — and folds into the server read.
Description#
The selector IS the sort key#
Because a selector is a value, the sort key can be chosen at runtime — which is the whole of click-to-sort:
entity Report { [MaxLength(80)] string Title; decimal Total; }
class Column<T> { public string Label; public Func<T, string> Value; }
[Composable] component Grid<T>(T[] rows, Column<T>[] columns) {
Column<T> sortBy = columns[0];
action SortBy(Column<T> c) { sortBy = c; }
render {
Stack {
Row {
foreach (var h in columns) {
Pressable(onClick: () => SortBy(h)) { Text(h.Label); }
}
}
foreach (var r in rows.OrderBy(sortBy.Value)) {
Row { foreach (var c in columns) { Text(c.Value(r)); } }
}
}
}
}The grid names no entity and no field. It works for every row type, because a generic class carries the selector and the selector carries the key.
Where it runs, and why you do not choose#
A sequence passed in has already been fetched, so sorting it is in-memory work over rows on screen. A query
member still has a query behind it, so .OrderBy on one folds into the server read — which is the right answer
when the query is paged, because sorting the client's window would order the wrong rows.
You never spell that difference. It follows from where the rows came from.
A runtime key over a server query#
Not supported: a server query's ORDER BY is compiled into SQL, so its key must be written out. Let the user pick
the column by sorting the rows the client holds, as above. The compiler says so if you try.
Examples#
Descending, and a computed key — anything the selector can express:
rows.OrderByDescending(r => r.Total)
rows.OrderBy(r => r.Total > 1000 ? "large" : "small")Errors#
| What you wrote | What you get |
|---|---|
rows.OrderBy(column.Run) where Run is an Action | needs a selector that RETURNS the key to sort by, and this one returns nothing. |
a Column<User> selector over Report rows | this selector reads a User, but the rows are Report. |
rows.OrderBy(x => x.Owner) (an entity) | a sort key must be a comparable value. |
Report.OrderBy(col.Value) (a server query) | a server query's sort key is compiled into SQL … sort the rows the client already holds. |
var copied = liveRows.ToList(); | 'copied' reads the live member 'liveRows', but 'copied' is not itself live — see below. |
⚠ Deriving from a query needs live. A non-live field is evaluated once, when the component is created, before
the query has loaded — so it keeps an empty value forever, and the page renders with no rows and no error. Write
live var copied = ….
See also#
- OrderBy / ThenBy —
OrderBy/ThenByon a SERVER query, compiled into SQL - Func<T, R> —
Func<T, R>as a value: what a selector IS - Generic classes — one
Column<T>for every row type - Skip / Take (paging) —
Skip/Take, and why a paged query sorts server-side