In OData, what does declaring a navigation property with ContainsTarget change about the contained entities' keys, identity and URLs?
answer
- an implicit entity set per parent
- keys unique within the parent only
- canonical URL starts at the container
- no top-level entity set of their own
- a partner leading back to the owner
basics
~20 sWith ContainsTarget, each parent gets an implicit entity set of its contained entities: their keys need be unique only within that parent, their canonical URL is the parent's plus the property and key, and no top-level entity set may also hold them.
solid answer
~40 sA containment navigation property, `ContainsTarget="true"`, says instances of the declaring type own its targets. Each `Order` then has an implicit entity set, `Orders(1001)/Lines`, so a line's key - say `LineNumber` - need be unique only within its order, and a line's canonical URL is `Orders(1001)/Lines(2)` rather than a top-level path. A contained entity cannot also belong to an entity set in the container, nor to a second containment relationship, so there is no `OrderLines` set for querying lines across all orders. A collection-valued containment property still needs a keyed entity type. A `Partner` back to the order is allowed and, for non-recursive containment, MUST be non-nullable; without one, a client has no reliable way to tell which order contains a line. Adding, changing or deleting a line MAY change the order's ETag.
go deeper
Recall that ContainsTarget makes the related entities owned by their parent, so they are addressed through the parent's URL rather than a top-level entity set.
Explain the mechanics: an implicit entity set per parent, keys unique only within the parent, and the canonical URL built from the container's URL plus the property and key.
Weigh the trade: short owner-relative keys and explicit lifecycle against losing a top-level collection, parent ETag changes, and the need for a non-nullable partner.
Judge where ownership boundaries sit in the domain model, since containment fixes them into URLs and identity that clients will depend on long after the first release.
## Containment versus an ordinary relationship An ordinary **navigation property** links two independent entities: an order points at a customer that lives in the `Customers` entity set and exists whether or not the order does. A **containment navigation property** - one declared with `ContainsTarget="true"` - says something stronger: instances of the declaring type **contain** the targets, which exist only inside their container. | Aspect | Ordinary navigation | Containment (`ContainsTarget="true"`) | |---|---|---| | Where targets live | Typically a top-level entity set, named by a navigation property binding | An implicit entity set per containing instance | | Key uniqueness | Within the top-level entity set | Within the containing instance's collection | | Canonical URL | `/Customers('C42')` | `/Orders(1001)/Lines(2)` | | Also in a top-level set? | Typically | **MUST NOT** be | | Reachable from several parents? | Yes | Only one containment relationship | ## What containment does to identity and addressing 1. **An implicit entity set per parent.** The CSDL says containment navigation properties "define an implicit entity set for each instance of its declaring structured type", identified by the read URL of the navigation property for that instance. `Orders(1001)/Lines` and `Orders(1002)/Lines` are two different entity sets. 2. **Keys are scoped to the parent.** Because an entity's key identifies it within an entity set, a line's `LineNumber` need be unique only within its order: line 2 can exist in every order. Without containment, the same lines in a top-level `OrderLines` set would need a key unique across all orders - typically a composite key such as `OrderID` plus `LineNumber`, or a service-wide surrogate key. 3. **The canonical URL starts at the container.** The URL conventions define it as the containing entity's canonical URL, then a type-cast segment if the property is declared on a derived type, then the navigation property segment, then the key if the property is collection-valued: `/Orders(1001)/Lines(2)`. 4. **Responses say so.** The context URL of a contained entity names the path through its container, for example `$metadata#Orders(1001)/Lines/$entity`. ## The constraints that come with it - An entity **cannot** be referenced by more than one containment relationship, and **cannot** both belong to an entity set declared in the container and be contained. - Entity types used in a **collection-valued** containment navigation property **MUST** have a key. - A navigation property binding's path may traverse containment navigation properties but **MUST NOT** end in one: contained entities are located by their container, not bound to a set. - Modifying, adding or deleting a contained entity **MAY** change the ETag of the parent entity, so a client holding the order's ETag can see its update rejected after a line changes. ## Partners and recursive containment A containment navigation property **MAY** declare a `Partner` leading back to the container. The CSDL warns that without one "there is no reliable way for a client to determine which entity contains a given contained entity", which bites when the contained entity can also be reached through a non-containment path. Two rules apply: - **Non-recursive containment** (orders containing lines): the partner **MUST NOT** be nullable - every line has an order. - **Recursive containment** (a type containing entities of its own inheritance hierarchy, such as categories containing sub-categories) defines a **tree**: the partner **MUST** be nullable, because the root has no parent, and single-valued, because each non-root node has one. ## When to use it Containment fits data whose identity is meaningless without its owner - order lines, a contract's clauses, the steps of a workflow instance. It buys short, owner-relative keys, URLs that show ownership, and a model that states the lifecycle dependency instead of leaving it to documentation. It costs the ability to treat contained entities as a top-level collection: a client reaches lines only through their orders, for example by reading `/Orders(1001)/Lines` or expanding lines while reading orders. If reporting across all lines is a core use case, an ordinary entity set with a composite key and a navigation property may serve clients better. In **OData 4.01** the target type of a **single-valued** containment navigation property need not declare a key - an order's single `Invoice` can be key-less. OData 4.0 requires a key on every entity type used this way.
- How does a client list order lines across all orders when lines are contained?Not through a top-level entity set, because contained entities cannot belong to one. The client reaches lines through their orders - `/Orders(1001)/Lines`, or by expanding the lines while reading orders. If cross-order queries over lines are a core need, that is a signal to model lines as an ordinary entity set with a composite key and a navigation property instead.
- What does recursive containment model, and what does it require of the partner?Containment between entity types in the same inheritance hierarchy, such as a `Category` containing child categories, which defines a tree. If it declares a partner leading back to the parent, that partner MUST be nullable, because the root has no parent, and single-valued, because every non-root node has exactly one parent.
Flats in apartment buildings: flat 3 exists in many buildings, so a flat is identified by its building's address followed by its number, and no city-wide register lists flats by their number alone.
saying these in an interview costs you the question
- Contained entities still need keys unique across the whole service.
- A contained order line can also be listed in a top-level OrderLines entity set.
- ContainsTarget just means the related entities are returned inline by default.
- Containment is only a cascade-delete flag and changes nothing about addressing.
- A contained entity may belong to two different containing parents at once.