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
Membershipvariable that happens to hold aCompanyMemberreadsOrganizationas anOrg, 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 wrote | refused because |
|---|---|
override Team Organization; where Team does not derive from Org | not 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 collection | only a single reference: a collection is a view over another table, not a column |
override Company Nothing; with no Nothing above | nothing to narrow — check the spelling |
Company Organization; without override | a 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;withorganOrgis refused, and the message names theistest 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 aValidationExceptionthat 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