# relations

> One entity points at another by declaring it as a member — that is the foreign key. The parent reads its children back through a collection member marked with ForeignKey. Never query children with a filter; go through the collection.

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

## Summary        {#summary}
A relation is two halves of one idea. The **child** points up by declaring the parent as a member — `Order Order;` —
which is the foreign key. The **parent** reads down through a collection member — `[ForeignKey(Order)] LineItem[]
LineItems;`. Declare both, and you can walk the graph in either direction.

## Signature      {#signature}
```osy syntax
entity <Child> {
  [Required] <Parent> <Parent>;                    // the FK — one row of the parent
}

entity <Parent> {
  [ForeignKey(<Parent>)] <Child>[] <Children>;     // the children pointing back at this row
}
```

## Description    {#description}

### How do I declare both sides of a relation?   {#two-halves}
The child's member IS the foreign key — there is no `OrderId` to declare and keep in step. You assign a row, not an
id, and you read a row back. The parent's collection member carries the **`[ForeignKey]`** attribute, naming the
relation it reads down — `[ForeignKey(Order)]` on the `Order`'s `Lines`:

```osy title="an order and its lines" test app=entity-relations
entity Order {
  [Required] string Code;
  decimal Total;
  [ForeignKey(Order)] LineItem[] Lines;   // read down: this order's lines
}

entity LineItem {
  [Required] Order Order;                  // point up: the order this line belongs to
  [MaxLength(200)] string Product;
  decimal Amount;
}

void AddLine(string orderCode, string product, decimal amount) {
  var order = Order.Single(o => o.Code == orderCode);
  var line = new LineItem { Order = order, Product = product, Amount = amount };
  //                        ^^^^^^^^^^^^^ assign the ROW, not an id
}
```

`[Required]` on the child's parent member means an orphan is impossible: a `LineItem` with no `Order` is a violation
at commit, not a row nobody notices for six months.

### Read children through the collection, never a filter   {#never-query-children}
This is the one rule people get wrong. To get an order's lines, go through the collection:

```osy title="reading a parent's children" test app=entity-relations
decimal OrderTotal(string code) {
  var order = Order.Single(o => o.Code == code);
  var total = 0m;
  foreach (var line in order.Lines) {     // the collection — connected to the order you already loaded
    total += line.Amount;
  }
  return total;
}
```

Do **not** write a standalone query filtered on the foreign key to fetch children. It looks equivalent and is not:
the collection is part of the object graph you already have, so it is loaded once and reused, while a separate query
is disconnected from the parent and re-runs every time you touch it. The collection is the connected path; a filtered
query is a second, unrelated result set that happens to contain the same rows.

### How do I let a reference be unset?   {#optional}
Leave off `[Required]` and the reference may be null — a `Ticket` that nobody is assigned to yet:

```osy title="an optional reference" test app=entity-relations
entity Person {
  [Required] string Name;
}

entity Ticket {
  [Required] string Title;
  Person Assignee;                     // may be null — an unassigned ticket is a real state
}

string AssigneeName(Ticket t) {
  return t.Assignee?.Name ?? "unassigned";   // null-safe, exactly as in C#
}
```

### What `[Required]` decides about deleting   {#required-and-delete}
A required reference says the child **cannot exist without its parent**, and the platform takes that literally:
deleting the parent deletes those children with it. An optional reference is the other answer — deleting the parent
leaves the child and clears its reference.

```osy syntax
entity Tag {
  [Required] Note Note;    // deleting the Note deletes this Tag
}

entity Draft {
  Note Note;               // deleting the Note leaves this Draft, with Note cleared
}
```

⚠ **This is where the decision is made, so it is worth making deliberately.** A cascade removes the children
**without asking whether the caller could have deleted them on their own** — someone allowed to delete a `Note` can
delete its `Tag`s by deleting the note, even where your rules never grant them `delete` on `Tag`. That is the
declaration doing what it says rather than a gap in it: the alternative would be a legal model that fails at run
time, with nothing you could write to fix it.

So if a child's removal should be governed separately from its parent's, do not make its reference required —
model it as optional and delete it explicitly.

## See also       {#see-also}
- [entity](https://osysharp.com/reference/entity/declaration/) — the entities a relation joins
- [constraints](https://osysharp.com/reference/entity/constraints/) — `[Required]` on a reference is what forbids an orphan
- [Where / Single / Count](https://osysharp.com/reference/query/where/) — querying the entities themselves
