# Entity

> `Entity` is a reference to a row of ANY type the app keeps — what a notification, a comment or a question "about some record" holds. It reads as its reader: a row they may not read reads null.

<!-- id: types-entity · area: types · stability: stable · html: https://osysharp.com/reference/types/entity/ -->

## Summary        {#summary}
`Entity About;` is a member that references **a row of any type** the app keeps — a ticket, an order, a draft — where
an ordinary reference (`Ticket Ticket;`) names one. It is what a kit holds when it is "about some record" it cannot
know the type of: a notification, a comment, a question.

It reads **as its reader**: a row the person reading it may not read reads `null` — not a shell carrying its id — so
`== null`, a type test and [`Records.Title`](https://osysharp.com/reference/stdlib/records/) all say nothing about it.

## Signature      {#signature}
```osy syntax
[Required] Entity About;                 // any row of any entity type — never a class or an interface value
Entity? Seen;                            // optional
[OnDelete(Remove)] Entity About;         // what deleting the row it references does to this one — see [OnDelete]

note.About = ticket;                     // any row
if (note.About is Ticket t) { … }        // C#'s declaration pattern, straight off the member
var t = note.About as Ticket;            // null when it is not a Ticket — or not readable
note.About == ticket                     // in a query too: compares the reference
note.About.Id                            // in a query too: the key it holds
```

## Description    {#description}

### What may it reference?   {#what}
Any row of any `entity` — the app's own and every kit's. Not a `class` value or an `interface`: those are not stored,
so there is nothing to reference. An `Entity` member accepts a row and refuses anything else where it is written.

### What does it read as?   {#reads-as}
The row, **read under the reader's own rules** — as every read is. Because the schema promises nothing about which
type a reference holds, it cannot promise that the reader may read it either, so a row the reader may not read reads
`null` rather than a row with its fields masked. Reading many — a list of notifications, each about some record — reads
the records **together, one query per type**, not one per row. SECURITY-INVARIANT
`an-entity-reference-reads-as-the-caller`.

What the member *holds* does not change with who reads it: a person who may not read the referenced row may still
write the row that holds the reference, and `[Required]` is satisfied by the key it holds.

### Which type is it?   {#type}
Test it with C#'s own `is` / `as` / cast. `if (x.About is Ticket t)` binds `t` straight off the member. The type is kept
beside the key, as a column named `<Member>Type` (`AboutType`) holding the entity's name, so a query can ask
`n.AboutType == "Ticket"` — and the compiler refuses a member of yours with that name.

### What happens when its row is deleted?   {#on-delete}
By default nothing: the member keeps the key and reads `null`. Say otherwise with [[entity-on-delete|`[OnDelete]`]]:
`Remove` deletes the holding row with it, `Clear` empties the member.

### What do I show for it?   {#facts}
Its record facts, which every row has: [`Records.Title(x)`](https://osysharp.com/reference/stdlib/records/) (what it is called),
`Records.Link(x)` (where it opens), `Records.Noun(x)` (what kind of thing it is) and `Records.MayRead(person, x)`.

## Examples       {#examples}

```osy title="a comment about any record, read by whoever reads it" test app=types-entity
[Principal] entity Person {
  [Required, MaxLength(100)] string Name;
  security { allow read when IsAuthenticated; }
}

[Title(Subject)]
entity Issue {
  [Required, MaxLength(200)] string Subject;
  bool Private;
  [Required] Person Reporter;
  security { allow read, create when IsAuthenticated where !Private || Reporter == user; }
}

[Title($"Invoice {Number}")]
entity Invoice {
  [Required, MaxLength(20)] string Number;
  security { allow read, create when IsAuthenticated; }
}

/// A comment about an issue or an invoice — or any record the app keeps later.
entity Comment {
  [Required, MaxLength(2000)] string Text;
  [Required, OnDelete(Remove)] Entity About;
  security { allow read, create when IsAuthenticated; }
}

/// What the reader sees of each comment: the record's title, or nothing when they may not read the record.
public List<string> CommentLines() {
  List<string> lines = [];
  foreach (var c in Comment.OrderBy(c => c.Text).ToList()) {
    var about = c.About == null ? "(a record you cannot see)" : Records.Title(c.About);
    if (c.About is Issue i && i.Private) { about = about + " (private)"; }
    lines.Add(about + ": " + c.Text);
  }
  return lines;
}

/// The comments about one record — the reference compared in the query.
public int CommentsOn(Invoice invoice) { return Comment.Count(c => c.About == invoice); }
```

## See also       {#see-also}
- [Records — what any record is called, where it opens, who may read it](https://osysharp.com/reference/stdlib/records/) — what any record is called, where it opens, and who may read it
- [[OnDelete]](https://osysharp.com/reference/entity/on-delete/) — what deleting the referenced row does
- [[Title] on an entity](https://osysharp.com/reference/entity/title/) — what each record is called
- [relations](https://osysharp.com/reference/entity/relations/) — a reference to ONE type, with its collection on the other side
- [Security.MayRead — may THIS person read this row, under their own rules](https://osysharp.com/reference/security/may-read/) — the reader's own rules, which every read obeys
