skip to content

In OData 4.01, how does a client write the URL for one entity by its key, including composite and string-valued keys?

level: juniorimportance: must knowfreq 32%

answer

  1. entity set, then parentheses
  2. names only for multi-part keys
  3. double the quote, encode the slash
  4. 4.01 key value as a segment

basics

~10 s

Append a key predicate to the entity set: Products(1), Employees('A1245'), OrderItems(OrderID=1,ItemNo=2). Inside a quoted string a single quote is doubled and a slash percent-encoded; OData 4.01 services may also accept Employees/A1245.

solid answer

~40 s

An OData entity is addressed by its entity set plus a **key predicate** in parentheses: `Products(1)`. A single-part key is written as the bare value, strings in single quotes (`Employees('A1245')`), and a single quote inside a string is doubled (`People('O''Neil')`) - percent-encoding it as `%27` does not escape it. A forward slash in a key must be percent-encoded as `%2F`, or it splits the path. Composite keys name every part: `OrderItems(OrderID=1,ItemNo=2)`, and the canonical URL lists them in the order the key definition declares. OData 4.01 adds an optional **key-as-segment** convention - `Employees/A1245`, `OrderItems/1/2` - with unquoted values; a 4.0 service only knows the parentheses form, so a client working against both should prefer parentheses or follow the URLs the service returns.

go deeper

for a junior

Write Products(1), a quoted string key and a composite key with named parts from memory, and know that /$value and /$count go after the path.

for a middle

Explain the escaping rules - doubled single quotes, an encoded slash - and why %27 does not escape a quote once the URL is normalised.

for a senior

Show how a client handles services on 4.0 and 4.01: canonical URLs for identity, parentheses as the safe default, key-as-segment precedence when a key collides with an operation name.

for a principal

Weigh adopting key-as-segment for familiarity against interoperability with 4.0 clients and the ambiguity it adds to every segment after a collection.

## The key predicate An OData service exposes **entity sets** (named collections such as `Products` or `Employees`) under a **service root URL**, which must end in a forward slash. To address one entity, a client appends a **key predicate** - the entity's key value in parentheses - to the entity set's URL: ```http GET https://sales.example.com/service/Products(1) GET https://sales.example.com/service/Employees('A1245') ``` - A **single-part key** is written as the bare value; the canonical form omits the property name (`Products(1)`, not `Products(ID=1)`, though the long form is also valid). - **String values** are enclosed in single quotes; numbers, GUIDs and other primitives use their own literal forms. - An **alternate key** (declared with the `Core.AlternateKeys` annotation) must name its property even when it has one part: `Employees(SSN='123-45-6789')`, so the service can tell it from the primary key. ## Composite keys and the canonical URL When an entity type's key has several properties, the predicate names each one: ```http GET https://sales.example.com/service/OrderItems(OrderID=1,ItemNo=2) ``` The **canonical URL** of a non-contained entity is the service root plus one segment: entity set name and key predicate, with multi-part keys listed in the order the key definition declares them. Other paths can reach the same entity - `Orders(1)/Items(OrderID=1,ItemNo=2)` follows a navigation property - but they are not canonical. When the navigation property's partner carries a **referential constraint** tying `OrderItem.OrderID` to `Order.ID`, the constrained part may be dropped: `Orders(1)/Items(2)`. The canonical URL never contains a type-cast segment, even for a derived type. For entities reached through a **containment** navigation property, the canonical URL instead runs through the containing entity. ## Escaping string keys OData parses URLs after percent-encoding normalisation, so an encoded character means the same as the plain one. Two rules follow: 1. A single quote inside a string literal is written as **two single quotes**: `People('O''Neil')`. `People('O%27Neil')` is invalid, because `%27` normalises back to a lone quote that ends the string. 2. A forward slash inside a value must be percent-encoded: `Categories('Smartphone%2FTablet')`. A raw slash is a path separator, so `Categories('Smartphone/Tablet')` splits into two meaningless segments. ## Key-as-segment in OData 4.01 OData 4.01 lets a service also support the **key-as-segment convention**, where the key value becomes its own path segment, unquoted: | Form | Parentheses style | Key-as-segment (4.01, optional) | |---|---|---| | Single key | `Employees('A1245')` | `Employees/A1245` | | Quote in key | `People('O''Neil')` | `People/O'Neil` | | Slash in key | `Categories('Smartphone%2FTablet')` | `Categories/Smartphone%2FTablet` | | Composite key | `OrderItems(OrderID=1,ItemNo=2)` | `OrderItems/1/2` | In segment form, single quotes are part of the value and need no doubling, slashes must still be encoded, and a composite key becomes one segment per key part in key-definition order. Because a bare segment could also be a bound function, an action or a type cast, a service supporting it must resolve a segment after a collection in this order: an OData `$` segment, then a qualified bound operation or type name, then an unqualified one from a default namespace, and only then a key value. Key-as-segment works only with the primary key, never an alternate key. The specification says such services **should** also accept parentheses, and otherwise must return each entity's URL in responses. ## What can follow the key Once an entity is addressed, further segments drill into it: - `Products(1)/Supplier` - follows a **navigation property** to a related entity. - `Products(1)/Name` - addresses one **property**; `Products(1)/Name/$value` returns its **raw value** (plain text for most primitive types) instead of a JSON wrapper. - `Categories(1)/Products/$count` - returns just the **number** of related entities as plain text. - `Categories(1)/Products/$ref` - addresses the **references** (links) between entities rather than the entities; a `DELETE` on such a reference unrelates them. Which options a client can add after `?` is a separate grammar from the path, and the entity model that defines keys and navigation properties is described in the service's metadata. ## Prefer the URLs the service hands back The resource-path rules are conventions that services **should** follow, not a guarantee. A service that departs from them is encouraged to document its paths with the `Core.ResourcePath` annotation, and any service **may** redirect from the canonical URL to its actual URL with `301 Moved Permanently` or `307 Temporary Redirect`. A robust client therefore builds key URLs from the conventions only as a fallback: it follows redirects, uses the `@id` or `@editLink` a response supplies when they differ from the computed values, and resolves a stored entity-id through `$entity?$id=` rather than guessing its path.

  • Why is Products(1) the canonical URL even when the client reached the entity through Categories(1)/Products(1)?
    The canonical URL of a non-contained entity is the service root plus a single segment: entity set name and key predicate, with no navigation path and no type-cast segment. Navigation paths are valid alternate addresses, but clients and services compare entities and build entity-ids from the canonical form. Contained entities are the exception: their canonical URL runs through the containing entity.
  • A 4.01 service supports key-as-segment, and a product's key equals the name of a bound function. What does Products/<that name> address?
    The bound operation. A service supporting key-as-segment must check a segment after a collection in order: an OData `$` segment, a qualified bound function, action or type name, then an unqualified one from a default namespace, and only then a key value. A key that collides is reachable only in parentheses form, which is one reason such services should keep supporting it.
  • How does addressing by an alternate key differ from addressing by the primary key?
    An alternate key, declared with `Core.AlternateKeys`, uses the same parentheses style but must name its properties even when it has one part - `Employees(SSN='123-45-6789')` - so the service can tell it from the primary key. Key-as-segment cannot carry an alternate key, because a bare segment has no property name.

saying these in an interview costs you the question

  • A single quote in a string key is escaped by writing it as %27.
  • A slash inside a quoted key value is safe because the quotes protect it.
  • Every OData 4.0 service accepts key-as-segment URLs like Employees/A1245.
  • The canonical URL keeps the navigation path the client used to reach the entity.
  • Key-as-segment can address an entity by its alternate key.