Osy#betaRun it on Osyrin: osyrin.com

Reference / Entity

override (a narrower reference)

override <Subtype> <Member>;

On a subtype, `override Company Organization;` narrows a reference the base declares to a type derived from the base's: the same member and the same column, typed narrower, so code holding the subtype reaches the narrower type's fields. A write that is not the narrower type is refused.

stable1 example compiled by CIentitymodelinheritancerelations

Summary#

A subtype may narrow a reference it inherits: override Company Organization; on CompanyMember : Membership says that every CompanyMember's Organization is a Company, where Membership only promised an Org. It is C#'s covariant override applied to a stored reference — the same member and the same column, typed narrower on the subtype and on every type below it. So m.Organization.Slug compiles when m is a CompanyMember, in a function, in LINQ and in a policy, with no is test at each use.

Signature#

entity <Subtype> : <Base> {
  override <NarrowerType> <Member>;   // <Member> is a reference <Base> declares; <NarrowerType> derives from its type
}

Description#

When do I need it?#

When you derive from a base whose references point at another base — most often a package's: a kit's Membership holds the kit's Organization, and your app derives its own Organization : Osysharp.Organizations.Organization with the fields it keeps. Without the override, every member's organization is typed as the kit's, and reaching your field means a test at each use (if (m.Organization is Organization o) …). With it, the subtype says once what it holds.

What does it change, and what does it keep?#

It changes the member's type on this subtype and below it. Everything else is the base's, because the column is:

  • No new column. The member stays the base's one column in the base's table; nothing in the database changes.
  • The base's attributes, default and nullability stand. An override names a type and nothing else, so [Required] override …, override Company Organization = …; and a computed => … are refused by name.
  • Code holding the base still sees the base's type. A Membership variable that happens to hold a CompanyMember reads Organization as an Org, as in C#.
  • A type below the subtype inherits the narrowing, and may narrow it again to something derived further.

Which types may it name?#

The override's type must be the base member's type or derive from it: narrowing promises every reader holding the subtype a row of the kind the base already promised. Refused, each with its own message:

you wroterefused because
override Team Organization; where Team does not derive from Orgnot narrower — the message lists every type here that derives from Org
override string Note;only a reference to an entity can be narrowed
override Company[] Organizations; over a collectiononly a single reference: a collection is a view over another table, not a column
override Company Nothing; with no Nothing abovenothing to narrow — check the spelling
Company Organization; without overridea second declaration of one column; the message suggests override

What happens on a write?#

The column must hold only the narrower type, so reads through the subtype are sound:

  • Where the compiler knows the value's type, assigning a wider one is a compile error — m.Organization = org; with org an Org is refused, and the message names the is test that makes it legal.
  • Where it cannot — a write through the base's type (Membership b = m; b.Organization = org;), or a bare id arriving from an import or the API — the write is refused when it is made, as a ValidationException that names both types. A row of the narrower type is written through the same handle as usual.

Two subtypes, two narrowings#

Siblings may narrow the same member to different types — CompanyMember to Company, TeamMember to Team. They are still the base's one column; each subtype holds only its own kind.

Examples#

entity Org {
  [Required, MaxLength(200)] string Name;
}

entity Company : Org {
  [MaxLength(40)] string Slug = "";
}

entity Membership {
  [Required] Org Organization;
  [MaxLength(200)] string Email = "";
}

// The same column, typed narrower: every CompanyMember's Organization is a Company.
entity CompanyMember : Membership {
  override Company Organization;
}

// No `is` test: the member is a Company wherever a CompanyMember holds it — in memory and in a query.
string SlugOf(CompanyMember m) { return m.Organization.Slug; }
int AtAcme() { return CompanyMember.Count(m => m.Organization.Slug == "acme"); }

See also#

  • entity Sub : Base — what a subtype shares with its base, and why a redeclared member is one column
  • relations — references, and a reference typed as a base holding any of its subtypes
  • sealed — how a type says no one may derive from it

Related

entity Sub : Base

Derives one entity from another. The subtype is its own type with its own name, its own security and its own workflows…

relations

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

sealed

Declares that no type may derive from this entity. A type is open unless it says otherwise, exactly as in C#. Sealing…