skip to content

OData

A REST-based protocol with a typed entity model, a $metadata document, and query options like $filter, $select and $expand. Interviewers ask in Microsoft or SAP stacks and when GraphQL is the rival.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

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.
open as a page

In OData, how does the service document at the service root differ from the $metadata document, and what does a client use each one for?

level: juniorimportance: must knowfreq 30%

basics

~20 s

The OData service document at the service root lists entry points: entity sets, singletons and opted-in function imports, each with a name and URL. The $metadata document describes the whole model in CSDL: types, keys, relationships, operations and annotations.

open as a page

Given the OData 4.01 request `GET Customers?$filter=City eq 'Lyon'&$orderby=Name&$top=10&$skip=20&$count=true`, what comes back, and in what order are the options applied?

level: juniorimportance: must knowfreq 34%

basics

~20 s

Customers 21 to 30 of those in Lyon, sorted by Name, plus the total number of Lyon customers. The service evaluates $filter, then $count, then $orderby, $skip and $top, whatever their order in the URL.

open as a page

In OData's multipart $batch format, how do you create an order and its lines so that they succeed or fail together?

level: middleimportance: must knowfreq 20%

basics

~20 s

Put both inserts in one change set, a nested multipart/mixed part whose operations each carry a Content-ID. POST the order as Content-ID 1, then POST the lines to $1/Items; the service applies all of them or none.

open as a page

How does an OData 4.01 client obtain an entity's ETag and use it with If-Match to update that entity safely?

level: middleimportance: must knowfreq 24%

basics

~20 s

An entity's ETag comes from the ETag header of a single-entity response or, per entity, the @etag control information (@odata.etag in 4.0). It goes back unchanged in If-Match on PATCH, PUT, DELETE or a bound action; stale gets 412, missing-but-required 428.

open as a page

In OData's entity data model, what distinguishes an entity type from a complex type, and how do you choose between them?

level: middleimportance: must knowfreq 30%

basics

~20 s

An OData entity type declares a key, so each instance has identity and can be addressed, created, updated and deleted on its own; a complex type is keyless and exists only as a property value inside the entity that holds it.

open as a page

In OData, how does a function differ from an action, and what changes when either one is bound rather than unbound?

level: middleimportance: must knowfreq 28%

basics

~20 s

An OData function MUST NOT have observable side effects, must return data and is called with GET; an action may change state, may return nothing and is called with POST. Bound operations are called on a resource; unbound ones are static, reached mainly through container imports.

open as a page

You are exposing an OData 4.01 entity set to many client teams; how do you keep arbitrary $filter, $expand and $orderby queries from becoming too expensive?

level: seniorimportance: must knowfreq 19%

basics

~20 s

Support only what you can serve cheaply and fail the rest (unsupported options MUST be rejected, SHOULD with 501), advertise limits with Capabilities annotations such as FilterRestrictions and ExpandRestrictions, and cap every collection with server-driven paging.

open as a page

What is OData's $batch request for, and why does a valid one return 200 OK even when a request inside it fails?

level: juniorimportance: should knowfreq 24%

basics

~20 s

OData's $batch packs many requests into one POST to the service root's $batch URL to save round trips. Its outer 200 OK only says the batch headers were valid and processing began; each inner request reports its own status.

open as a page

In an OData entity container, how does an entity set differ from a singleton, and when would you model a resource as a singleton?

level: juniorimportance: should knowfreq 18%

basics

~20 s

An OData entity set is a named top-level collection of entities addressed by key; a singleton is a named top-level single entity addressed by its name alone, suited to one-per-service resources such as the order desk's settings or the caller's own profile.

open as a page

Handed an unfamiliar OData service, how can you tell from its responses whether it speaks OData v2 or OData v4?

level: juniorimportance: should knowfreq 14%

basics

~20 s

Read the version header and the JSON envelope. OData v2 sends DataServiceVersion and wraps JSON in a "d" object with __metadata on each entry; OData v4 sends OData-Version (4.0 or 4.01) and puts a collection in a top-level "value" array.

open as a page

What does an OData 4.01 JSON error response body contain, and how does a service signal an error that occurs mid-response?

level: middleimportance: should knowfreq 15%

basics

~20 s

One JSON object whose single error member holds code (service-defined, language-independent) and message (human-readable), plus optional target, details and innererror. An error after 200 OK is signalled by leaving the payload malformed, optionally with an OData-Error trailer.

open as a page

In OData 4.01 JSON, what do the metadata=minimal, metadata=full and metadata=none format parameters change in a response?

level: middleimportance: should knowfreq 18%

basics

~20 s

The metadata format parameter sets how much control information the service writes. Minimal omits whatever metadata lets a client compute but keeps context, etag, count, nextLink and deltaLink; full writes every id, link and type explicitly; none keeps only nextLink and count.

open as a page

In OData 4.01, what do the Prefer preferences return=minimal, return=representation and respond-async ask a service to do?

level: middleimportance: should knowfreq 14%

basics

~20 s

OData's Prefer preferences are RFC 7240 hints: return=minimal asks for no response body, return=representation for the modified resource, respond-async for 202 Accepted plus a status monitor. A service must ignore preferences it does not support, so clients check the status and Preference-Applied.

open as a page

In OData's entity data model, how is a relationship like Order-to-Customer declared, and what do Partner and navigation property bindings tell a client?

level: middleimportance: should knowfreq 14%

basics

~20 s

An OData relationship is a NavigationProperty typed as another entity type or a collection of it. Partner names the inverse property that leads back; a NavigationPropertyBinding on the entity set says which entity set holds the related entities.

open as a page

In an OData metadata document, how does a vocabulary annotation apply a term to a model element, and what does Core.Computed tell a client?

level: middleimportance: should knowfreq 12%

basics

~20 s

An OData annotation applies a vocabulary term to a model element, nested inside it or targeted from an Annotations block, with an optional qualifier. Core.Computed marks a property the service generates on insert and update; a value the client sends is ignored.

open as a page

In OData v4, how does `Customers?$filter=Orders/any(o:o/Amount gt 500)` differ from `Customers?$expand=Orders($filter=Amount gt 500)`?

level: middleimportance: should knowfreq 17%

basics

~20 s

The any filter returns only customers who have at least one order over 500, with no orders inlined. The expand filter returns every customer, each carrying only its orders over 500 — possibly an empty list.

open as a page

In OData 4.01, how do you fetch one city's customers, each with only their five latest orders, and how do nested $expand options behave?

level: middleimportance: should knowfreq 26%

basics

~10 s

Put options in parentheses after the navigation property: Customers?$filter=City eq 'Lyon'&$expand=Orders($orderby=OrderDate desc;$top=5). The semicolon-separated nested options apply to each customer's own orders, not to the whole result.

open as a page

How does OData 4.01 server-driven paging work, and what must a client do with the next link and the maxpagesize preference?

level: middleimportance: should knowfreq 21%

basics

~20 s

An OData 4.01 service may return part of a collection with a next link; the client follows it unchanged until a page has none. Prefer: maxpagesize=N only requests a page size — the service may apply it or choose another.

open as a page

How do an OData client and service agree on a protocol version, and how did v2's DataServiceVersion headers differ from v4's OData-Version and OData-MaxVersion?

level: middleimportance: should knowfreq 11%

basics

~20 s

Both lines pair a version header with a ceiling. In v2 a server answers with the lowest version that serves the request, capped by MaxDataServiceVersion; in v4 the service MUST answer with the greatest version it supports that does not exceed OData-MaxVersion.

open as a page

An OData client sends a ten-request multipart $batch and gets back four response parts, the last an error. What happened, and how does continue-on-error change that?

level: seniorimportance: should knowfreq 10%

basics

~20 s

A multipart OData batch runs in order and, by default, stops at the first error, which becomes the last part; the six missing requests never ran. Prefer: odata.continue-on-error asks the service to report the error and keep going.

open as a page

In an OData 4.01 JSON batch, how do atomicityGroup and dependsOn decide what runs together, in what order, and what fails?

level: seniorimportance: should knowfreq 9%

basics

~20 s

Requests sharing an atomicityGroup must be adjacent and succeed or fail together. dependsOn names earlier ids or groups that must succeed first; anything undeclared may run in any order or in parallel, and a dependent whose prerequisite failed gets 424.

open as a page

In OData, what does declaring a navigation property with ContainsTarget change about the contained entities' keys, identity and URLs?

level: seniorimportance: should knowfreq 10%

basics

~20 s

With 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.

open as a page

A generic OData client is building a grid for an unfamiliar entity set; how does it learn from $metadata which filtering, sorting, paging and inserts the service allows?

level: seniorimportance: should knowfreq 9%

basics

~20 s

An OData client reads Capabilities vocabulary annotations on the entity set in $metadata: FilterRestrictions, SortRestrictions, TopSupported, InsertRestrictions and others. Absence means paging, counting and expand are assumed, inserts are not, and the service may still refuse a request.

open as a page

Should an OData client generate typed proxies from $metadata at build time or discover the model at runtime, and how does each cope with model changes?

level: seniorimportance: should knowfreq 11%

basics

~20 s

Typed OData proxies generated from $metadata give compile-time checks but freeze the model; runtime discovery adapts but gives up types. Either way, clients must tolerate OData's safe additive changes and can detect drift through the metadata ETag and, in 4.01, $schemaversion.

open as a page

You must move an OData v2 service and its existing clients to OData v4; what breaks on the wire, and how would you stage the migration?

level: seniorimportance: should knowfreq 8%

basics

~20 s

Almost every client-visible surface breaks: payloads, query syntax, relationship URLs, the MERGE method, the model's associations and service operations. Run a v4 service at a new root beside v2, migrate clients one by one, and retire v2 once its traffic is gone.

open as a page

In OData 4.01, how does a CSDL JSON metadata document differ from CSDL XML for the same model, and how does a client request each?

level: middleimportance: nice to knowfreq 7%

basics

~20 s

In OData 4.01, CSDL XML (root edmx:Edmx) and CSDL JSON (one object with $Version and $EntityContainer) describe the same model; JSON omits defaults, and some defaults differ, such as nullability. A plain GET $metadata returns XML; application/json asks for JSON.

open as a page

What must an OData client's JSON parser change when a service moves from OData v2 verbose JSON to OData v4 JSON?

level: middleimportance: nice to knowfreq 9%

basics

~20 s

Nearly every structural assumption. OData v4 drops the "d"/"results" envelope and __metadata, reports counts and paging as @odata.count and @odata.nextLink, sends Int64 and Decimal as JSON numbers by default, writes dates as ISO 8601-style strings, and omits computable links.

open as a page

How does an OData 4.01 service run a long $batch asynchronously, and what may the client see before the whole batch has finished?

level: seniorimportance: nice to knowfreq 4%

basics

~20 s

With respond-async on the batch POST, the service may answer 202 Accepted with a status-monitor Location. Polling it can yield interim results: a multipart response ending in a 202 part, or a JSON response with nextLink. Change sets never return 202.

open as a page

showing 1–30 of 33