Summary#
cost-page-ships-whole-rows-for-few-fields is a CONSIDER finding: a page reads whole rows of an entity with many
fields, and its list shows two or three of them. Every row still carries every field to the browser. A page's answer
is bounded by its size — about 2 MB (How many rows a page is sent) — so the fields nobody sees take the room of rows the page
could have shown.
Select the fields the page shows into a small class. The loop reads the same names, and each answer carries several times the rows whole rows would:
entity Shipment {
[Required, MaxLength(24)] string Reference;
decimal Total;
DateTime PlacedAt;
[MaxLength(80)] string? Carrier;
[MaxLength(120)] string? Tracking;
[MaxLength(400)] string? Note;
[MaxLength(80)] string? Street;
[MaxLength(40)] string? City;
[MaxLength(12)] string? Postcode;
[MaxLength(40)] string? Country;
decimal Weight;
int Parcels;
bool Fragile = false;
DateTime? DeliveredAt;
security { allow read, create when IsAnonymous || IsAuthenticated; }
}
class ShipmentRow {
public string Reference;
public decimal Total;
}
[Route("/shipments")] [Render(CSR)] [AllowAnonymous]
component ShipmentsPage() {
live var shipments = Shipment.OrderByDescending(s => s.PlacedAt).Take(10000)
.Select(s => new ShipmentRow { Reference = s.Reference, Total = s.Total }).ToList();
render {
Stack(gap: 2, p: 6) {
foreach (var s in shipments) { Text($"{s.Reference} — {s.Total}"); }
}
}
}Signature#
class Row { public T1 A; public T2 B; }
live var rows = Entity.….Select(x => new Row { A = x.A, B = x.B }).ToList();Description#
What it costs#
Measured on a 71-field order: a whole row is 1,813 bytes on the wire, and a Select into a class of three fields is
353. In one answer of about 2 MB that is about 1,150 whole rows against about 5,900 projected — and the database reads
three columns instead of seventy-one. The finding prints the same two numbers for your entity, estimated from the
fields it declares, so you can judge whether the difference matters for this page.
When does it fire?#
On a component field that reads whole rows of an entity with at least twelve stored fields, when:
- the read is a list (no
Select, noInclude, no one-row or aggregate verb) that asks for more rows than one answer of whole rows holds — noTake, or aTake(n)larger than that (aTake(5)shows five rows whatever they carry), - it is used only as the source of
foreachloops in the render, - each loop's row is used only to read a field (
s.Reference), never handed whole to an action, a child component or a comparison, and never assigned to, and - the fields read are at most five, no more than a third of the entity, and none is a reference to another entity.
When is it silent?#
When any of those does not hold, because then a projection is not the one-line change: a row handed to an action may
need any of its fields, an edited field needs the row, and a reference shown through (s.Customer.Name) needs the
include. It is also silent on a narrow entity, where the whole row is already small.
Why CONSIDER?#
Because the page is correct as written. For a table that stays small the whole row costs nothing anyone will notice; for one that grows, the projection is what lets the same answer hold several times the rows.
See also#
- How many rows a page is sent — how much a page is sent, and why it is a size
- A list or table with no bound — a list with no bound at all
- The reactivity & lifecycle model —
live varand when a page re-reads