Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

A page query with no bound

live var n = Entity.Count(x => …); // a number, over every row — not Entity.ToList().Count

osy lint's ui-page-query-without-a-take (CONSIDER) is the catch-all: a page query reads every row of an entity with no Take, and the page uses it some other way than a list, a dropdown or a chart. A page query is sent about 2 MB of rows an answer, so whatever the page computes from it is computed over the first answer once the table is larger. Bound it, narrow it, or ask for the number itself.

stable2 examples compiled by CIuilintquerylimits

Summary#

ui-page-query-without-a-take is a CONSIDER finding: a page holds a read of every row of an entity and uses it for something other than drawing a list, a choice or a chart — a count, a total, a row handed to another component. A page query is sent about 2 MB of rows an answer (How many rows a page is sent), so a count taken from the list stops where the answer does.

When the page needs a number, ask for the number; it is answered over every row:

entity Visit {
  DateTime At;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

[Route("/visits")] [Render(CSR)] [AllowAnonymous]
component VisitsPage() {
  live var visits = Visit.Count();

  render { Text($"{visits} visits"); }
}

Signature#

live var n = Entity.Count(x => …);              // a number over every row
live var rows = Entity.….Take(n).ToList();      // or a bound the author chose

Description#

When does it fire?#

On a component field read from an entity with no Take that the render reads, when none of the three specific rules applies — it is not a list's rows (A list or table with no bound), not a choice's options (A picker over every row — the SearchPicker) and not a chart's data (A chart over every row).

When is it silent?#

When the read is bounded (a Take, a one-row read, an aggregate, a child collection pinned to one parent), and when the page never draws it at all — a read only an action uses, such as an export. An export of every row belongs in a server function, which is never capped.

An editor's list#

A page that adds rows of the entity it lists before a Save is told here rather than by the list rule, because the list rule's remedy — a Take — would hide the row just added: an unsaved row joins a list only while the list is every row of its type. When the set is a handful by construction, say so on the member, with the reason:

entity Shelf {
  [Required, MaxLength(40)] string Name;
  int Rank;
  security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}

[Route("/shelves")] [Render(CSR)] [AllowAnonymous]
component ShelvesEditor() {
  // Every shelf: one added here is on the list before Save only while the list is every row. A shop has a handful.
  [SuppressWarning("ui-page-query-without-a-take")]
  live var shelves = Shelf.OrderBy(s => s.Rank).ToList();

  action Add() { UnitOfWork.Mark("Add a shelf"); new Shelf { Name = "New shelf", Rank = shelves.Count + 1 }; }
  action Save() { UnitOfWork.Commit(); }

  render {
    Stack(gap: 2) {
      foreach (var s in shelves) { Text(s.Name); }
      Button("Add a shelf", onPress: Add);
      Button("Save", onPress: Save);
    }
  }
}

When it can grow, edit it a parent at a time — a parent's collection takes a new child too — or commit each row as it is made and bound the list with .OrderBy(…).Take(n).

Why CONSIDER?#

Because it is the widest net: the linter sees that the page holds a read with no bound, not what the page does with it. A component that pages its own rows still holds an unbounded read, and the remedy — a Take, a Where — is cheap.

See also#

Related

How many rows a page is sent

A page's query is sent bounded. Its own .Take(n) is the ceiling: the browser may ask for fewer of those rows, never…

A list or table with no bound

osy lint's ui-unbounded-list-does-not-page (SHOULD) fires when a page draws a list with foreach from a query that reads…

The reactivity & lifecycle model

How an Osy# component comes alive and stays in sync: declarations are live value bindings, `on mount`/`on unmount` are…