# Select the fields a page shows

> osy lint's cost-page-ships-whole-rows-for-few-fields (CONSIDER) fires when a page reads whole rows of a wide entity and shows only a few of their fields. One answer to a page's query is about 2 MB, so whole wide rows fill it quickly; a Select of the fields the page shows into a small class fits several times the rows in the same answer, and the database reads only those columns. The finding quotes both row counts.

<!-- id: ui-narrow-page-reads · area: ui · stability: stable · html: https://osysharp.com/reference/ui/narrow-page-reads/ -->

## Summary        {#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](https://osysharp.com/reference/ui/read-limits/)) — 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:

```osy title="a list of shipments that reads only what it shows" test app=ui-narrow-page-reads
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      {#signature}

```osy syntax
class Row { public T1 A; public T2 B; }
live var rows = Entity.….Select(x => new Row { A = x.A, B = x.B }).ToList();
```

## Description    {#description}

### What it costs   {#cost}

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?   {#fires}

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`, no `Include`, no one-row or aggregate verb) that asks for more rows than one answer
  of whole rows holds — no `Take`, or a `Take(n)` larger than that (a `Take(5)` shows five rows whatever they carry),
- it is used only as the source of `foreach` loops 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?   {#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?   {#tier}

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        {#see-also}

- [How many rows a page is sent](https://osysharp.com/reference/ui/read-limits/) — how much a page is sent, and why it is a size
- [A list or table with no bound](https://osysharp.com/reference/ui/unbounded-lists/) — a list with no bound at all
- [The reactivity & lifecycle model](https://osysharp.com/reference/ui/reactivity/) — `live var` and when a page re-reads
