Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Query

Sorting rows the client holds

rows.OrderBy(column.Value) — sort a passed-in sequence by a selector chosen at runtime

A sequence the client already holds — a component's `T[]` rows parameter, a `List<T>` — sorts with `OrderBy` / `OrderByDescending`, and the key is named by a SELECTOR, never by a string. That is what lets the USER pick the sort: a column already carries its selector, so naming the column names the sort.

preview1 example compiled by CIqueryuisortingauthoring

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 form

There 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> value

The 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 wroteWhat you get
rows.OrderBy(column.Run) where Run is an Actionneeds a selector that RETURNS the key to sort by, and this one returns nothing.
a Column<User> selector over Report rowsthis 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#

Related

OrderBy / ThenBy

Sort a query by one key or several. `OrderBy`/`OrderByDescending` start the sort, `ThenBy`/`ThenByDescending` add…

Func<T, R>

A parameter or class field typed `Func<T, R>` takes a lambda and can be invoked for a result, so reusable code can be…

Generic classes

A class can declare type parameters, so one shape serves every type it is used with instead of being copied per entity…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

Skip / Take (paging)

Page a query with Skip(n) (OFFSET) and Take(m) (LIMIT). The count can be a compile-time constant OR a runtime integer —…