# Extension members

> A `partial entity X { … }` in a bridge package or in the app adds OPTIONAL members to an entity another package declares — a reference (a column on its table) and its inverse collection — with no link entity in between; the app may also add the list of its own rows that reference it.

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

## Summary        {#summary}
Two kits that do not know each other can be related by a third package — a **bridge** — or by the app, without a link
entity: a `partial entity` adds an optional reference to one kit's entity and its inverse collection to the other's.

```osy syntax
// in a bridge package that uses both kits
partial entity Quote       { Opportunity? Opportunity; }                    // a column on Quote's table
partial entity Opportunity { [ForeignKey(Opportunity)] Quote[] Quotes; }    // navigation: no column
```

From then on they are ordinary members, for the bridge's code and the app's alike: `q.Opportunity = o;`,
`o.Quotes.Sum(q => q.Total)`, a report from `Quote` grouped by `Opportunity.Name`.

## Signature      {#signature}
```osy syntax
partial entity <Entity> {
  <Type>? <Member>;                              // a reference or a value nobody has to supply
  <Type> <Member> = <default>;                   // …or one with a default
  [ForeignKey(<Back>)] <Element>[] <Members>;    // the inverse collection of a reference <Element>.<Back>
  security { deny <ops> <Member> when …; }       // a bridge may only take access away, from what it adds
}
```

## Description    {#description}

### What it is   {#what}
A `partial entity` names an entity a `use`d package declares, and its members move onto that entity's own declaration
before anything is compiled. So each one is an ordinary member of the owner:

- a **reference or a value** is a column on the owner's table — schema evolution adds it on the compile that first sees
  it (the owner's existing rows read it as `null`), and every later compile keeps it and its data;
- a **collection** is navigation only: the live list of the rows whose reference points back, with nothing stored. It
  updates before a commit like any collection, so it is the right way to list them (not a query on the back-reference);
- **Explore and Reports** walk it, because they follow declared relations — a report rooted at `Quote` can group by
  `Opportunity.Name`, and `Opportunity › Quotes › Total` is offered as a field;
- each member remembers the file that declared it, so the model says which package (or the app) added it.

The owner's own code was compiled without the member and never names it. Generic readers — Explore, Reports, Fields,
Privacy, `osy model` — see it, because they read the model rather than the owner's source.

### Optional only   {#optional}
The owner creates rows with code that has never heard of the member, so a member every row must have could never be
supplied. A member is optional when it is nullable (`T?`), has a default (`= …`), is a collection, or is a number or a
`bool` (whose zero is a value). The compiler refuses everything else where it is written:

```text
`Quote.Opportunity` is an extension member — added to `Quote` by package `Acme.Crm.Cpq` — so it must be optional:
`Acme.Cpq` creates `Quote` rows with code compiled without it, and could never supply it. It carries `[Required]` with
no default. Write `Opportunity? Opportunity;` (none until something sets it), or give it a default
(`Opportunity Opportunity = …;`).
```

For the same reason it may not carry `[Unique]`, `[Key]` or `[Id]`: those would refuse writes the owner's code makes.

### One name, one owner   {#conflicts}
A name the owner (or its base) already declares cannot be added again, and two extensions cannot add the same name to
one entity. Both are refused naming both sides — which is also how a later version of the owner that adds the same name
is caught, on the compile after the update:

```text
`Quote.Opportunity` is added by both package `Acme.Crm.Cpq` and package `Acme.Deals` — two extensions cannot add one
name to one entity. Drop one of the two from this app's `use` lines, or rename one of them.

`Quote.Total` is declared by `Quote` itself and added again by package `Acme.Deals` — one name on one table cannot have
two owners. Rename the extension (e.g. `DealsTotal`), or use the `Total` `Quote` already has.
```

### Security   {#security}
The member is a column of the **owner's** rows, so the owner's `security { }` decides every read and write of it:
reading `o.Quotes` returns the quotes the reader may read, and writing `q.Opportunity` needs `update` on the quote.
Whoever extends may only take access **away**:

- a **bridge** may write `security { deny <ops> <Member> when …; }` over the members it adds — appended to the owner's
  own rules. An `allow`, a `default`, or a rule over a member it did not add is refused:
  ```text
  Package `Acme.Crm.Cpq` extends `Quote`, so it may only take access AWAY, and only from what it adds: `deny <ops>
  <Member> when …;` naming one of `Opportunity`. Who may read or write a `Quote` row is `Quote`'s own `security { }`
  (and the app's `partial entity Quote`).
  ```
- the **app** states its rules as always, with `partial entity Quote { security { … } }` (see
  [[Authority]](https://osysharp.com/reference/security/contribution-authority/) for how far an app may change a package's rules).

### Whose entity can be extended   {#where}
An entity a **package** declares — a kit, or a package of your own. Not:

- **one of the platform's own tables** (the core baseline, a capability the platform ships): `partial entity X` over one
  of those may state `security { }` and nothing else, and the refusal says to put the reference on your own entity
  instead;
- **your own entity** — add the member to its own `entity X { … }` block;
- **from the app, a COLUMN naming one of the app's own types** — the package's table is compiled before the app's types
  exist, so put the reference the other way round, on your entity:
  ```text
  `Quote.Deal` names `Deal`, which this app declares — but `Quote` is a package's table, compiled before the app's own
  types exist, so an extension member on it can name only what the packages declare. Point the reference the other
  way: declare `Quote? Quote;` on your `Deal`.
  ```
  Its inverse is the exception — see [the app's list of its own rows](#app-list) below.

### The app's list of its own rows   {#app-list}
Once your entity references a package's — `[Required] Conversation Conversation;` on your `Ticket` — the package's
entity can carry the list of your rows that point at it:

```osy syntax
partial entity Conversation { [ForeignKey(Conversation)] Ticket[] Tickets; }
```

It is navigation, not a column, so nothing in the package's table changes: the compiler emits it in your app's pass,
over your own reference. It is an ordinary collection — it shows a ticket created in the same unit of work before
anything commits, loads on first touch, and `Include` pre-loads it — so `conversation.Tickets` is the way to read
them, not `Ticket.Where(t => t.Conversation == conversation)`. (A query over a SET of conversations at once —
`Ticket.Where(t => ids.Contains(t.Conversation.Id))` — is still a query: it is one statement where a list per
conversation would be one load each.)

`[ForeignKey(…)]` names your entity's reference to the package's entity; without it, or naming something else, the
compiler says which member to name:
```text
`Conversation.Tickets` lists the app's `Ticket` rows — a list on a package's entity is the inverse of a reference your
`Ticket` holds, so it names that reference: `[ForeignKey(Conversation)] Ticket[] Tickets;`.
```

### Deleting the package's row   {#delete}
Your reference to a package's entity is a real foreign key, whether or not you declare its list. Deleting the
package's row — from your code, or from the package's own, which has never heard of your types — never deletes yours:

| your reference | deleting the package's row… |
|---|---|
| required (`[Required] Conversation Conversation;`) | is refused while one of your rows points at it |
| optional (`Conversation? Conversation;`) | empties the reference on your rows |

A reference between your OWN entities differs on purpose: there a required reference deletes its rows with the parent
(see [relations](https://osysharp.com/reference/entity/relations/)), because both sides are yours.

### When the key arrives over existing rows   {#dangling}
An app compiled before its references to a package's entity carried a key — or one that turns an id into a reference —
may hold rows naming a package row that has since been deleted: nothing stopped it then. The compile that adds the key
settles those rows first, in the same statement:

| your reference | a row naming a package row that does not exist… |
|---|---|
| optional (`Conversation? Conversation;`) | is emptied — the reference reads `null`, which is what an optional reference to nothing means |
| required (`[Required] Conversation Conversation;`) | refuses the compile, and changes nothing, naming the rows |

A refusal names what you need to fix the data — the entity and member, each row's id and the id it names (the first
twenty), and the way out:

> `Schema evolution failed: 23503: Ticket.Conversation is required, and 2 Ticket row(s) name a Acme.Talk.Conversation
> that does not exist, so its foreign key cannot be added over them. Point each at a Acme.Talk.Conversation that exists
> or delete it, or make the reference optional (`Acme.Talk.Conversation? Conversation;`), whose missing rows are
> emptied; then compile again. The rows (Ticket id -> missing Acme.Talk.Conversation id): 0b1c… -> 77e2…, 4f0a… -> 9d13….`

`osy compile` prints this line as it is; the database's name and the statements it ran stay in the platform's log.

The same holds for every key the platform adds over rows that were already there, between your own entities too.

### What it is not   {#not}
It does not change the owner's behaviour: a partial may not declare an invariant, a method or a constructor. A bridge
adds data and navigation; the owner's rules, events and pages stay its own. To change those, take over the kit's page,
handle its events, or fork it into your project's `kits/`.

## Examples       {#examples}

### The app's own field on a kit's entity   {#app}
The app adds a member to the Identity kit's `Principal` and reads it like any other.

```osy title="a member of the app's own on a kit's entity" test app=entity-extension-members
app Club {
  use Osysharp.Identity;
  model "model/**/*.osy";
}

using Osysharp.Identity;

[Role] enum ClubRole { Authenticator, Member, Steward }

/// What a member likes to be called — the app's own field on the Identity kit's `Principal`. Optional, as every
/// extension member is: the kit creates principals with code that has never heard of it.
partial entity Principal { [MaxLength(60)] string? Nickname; }

public string Greeting(Principal p) { return "Hello, " + (p.Nickname ?? p.DisplayName) + "!"; }
```

Every kind of account derived from `Principal` — the UserAccounts kit's `User`, an app's `Member : Principal` — shares its
table, so each of them has `Nickname` too.

### The app's list of its own rows on a kit's entity   {#app-list-example}
The app's `Note` references the Identity kit's `Principal`; the kit's entity gains the list of notes each one wrote.

```osy title="a list of the app's rows on a kit's entity" test app=entity-extension-members-app-list
app Club {
  use Osysharp.Identity;
  model "model/**/*.osy";
}

using Osysharp.Identity;

[Role] enum ClubRole { Authenticator, Member, Steward }

/// A note a member wrote — the app's own entity, referencing the kit's `Principal`.
entity Note {
  [Required, MaxLength(200)] string Text;
  [Required] Principal Author;
  security { allow read, create when IsAuthenticated; }
}

/// Every note a principal wrote: the inverse of `Note.Author`, on the kit's entity.
partial entity Principal { [ForeignKey(Author)] Note[] Notes; }

public int NotesBy(Principal p) { return p.Notes.Count(); }
```

Deleting a principal who wrote a note is refused, because `Note.Author` is required; make it `Principal? Author;` and
the delete empties it instead.

### A bridge relating two kits   {#bridge}
The whole of a bridge between a CRM kit and a CPQ kit — its package `use`s both, and neither knows it exists:

```osy title="Acme.Crm.Cpq — model/Bridge.osy" syntax
namespace Acme.Crm.Cpq;
using Acme.Crm;
using Acme.Cpq;

/// The opportunity a quote prices — a quote made in CPQ alone has none.
partial entity Quote {
  Opportunity? Opportunity;
  security { deny read Opportunity when IsAnonymous; }   // only ever tighter than Quote's own rules
}

/// Every quote made for this opportunity.
partial entity Opportunity { [ForeignKey(Opportunity)] Quote[] Quotes; }

/// A quote for an opportunity — the bridge writes the member it added.
public Quote QuoteFor(Opportunity o, decimal total) {
  return new Quote { Title = o.Name, Total = total, Opportunity = o };
}

/// What an opportunity is quoted at — through the inverse collection.
public decimal Quoted(Opportunity o) { return o.Quotes.Sum(q => q.Total); }
```

The platform's own tests compose exactly these three packages and assert each sentence above
(`Platform.Tests/Runtime/Function/ExtensionMembersTests.cs`).

## See also       {#see-also}
- [relations](https://osysharp.com/reference/entity/relations/) — references and the `[ForeignKey]` collection an extension adds
- [security { }](https://osysharp.com/reference/security/entity-security/) — the owner's `security { }`, which governs the member
- [[Authority]](https://osysharp.com/reference/security/contribution-authority/) — how far an app's `partial entity` may change a package's rules
- [entity Sub : Base](https://osysharp.com/reference/entity/inheritance/) — a subtype of a package's entity, the other way to add members (for one kind of row)
- [Entity](https://osysharp.com/reference/types/entity/) — a reference to a row of any type, when the target is not one entity
