Osy#betaRun it on Osyrin: osyrin.com

Reference / Types

Entity

Entity <Member>; · Entity? <Member>; · [OnDelete(Remove|Clear|Keep)] Entity <Member>;

`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.

stable1 example compiled by CItypesentitymodelrelations

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 all say nothing about it.

Signature#

[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#

What may it reference?#

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?#

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?#

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?#

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?#

Its record facts, which every row has: Records.Title(x) (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#

[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#

Related

Records — what any record is called, where it opens, who may read it

`Records.*` answers facts about a row of ANY type from what the app declared — what it is called (`[Title]`), where it…

[OnDelete]

`[OnDelete]` on an `Entity` member says what deleting the row it references does to the row holding it: Remove deletes…

[Title] on an entity

`[Title]` on an entity says what each of its records is called — the line a notification, a search result or a link…

relations

One entity points at another by declaring it as a member — that is the foreign key. The parent reads its children back…

Security.MayRead — may THIS person read this row, under their own rules

`Security.MayRead(person, row)` answers whether a given person may read a given row under THEIR OWN security — the…