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?
answer
- options in parentheses after the property
- semicolons, not ampersands
- applied per parent
- $levels for self-references
basics
~10 sPut 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.
solid answer
~40 sThe outer `$filter` chooses customers; `$expand=Orders(...)` inlines each customer's related orders, and the semicolon-separated options inside the parentheses shape that related collection for every customer separately: `$orderby=OrderDate desc;$top=5` keeps each customer's five newest, `$select` trims the order properties, `$count=true` adds `Orders@count`, and a nested `$expand` reaches further, into each order's items. A `$filter` inside the expand removes orders from the inline list; it never removes customers. In 4.01 the expand options are `$filter`, `$select`, `$orderby`, `$skip`, `$top`, `$count`, `$search`, `$expand`, `$compute` and `$levels`; `$levels` recursively expands a navigation property whose target has the source's type, such as a manager hierarchy. Expanded navigation properties are returned even when the outer `$select` does not list them.
code
http · 4 linesGET /odata/Customers?$filter=City%20eq%20'Lyon'&$select=ID,Name&$expand=Orders($select=ID,OrderDate,Amount;$orderby=OrderDate%20desc;$top=5;$count=true) HTTP/1.1
Host: api.example.com
Accept: application/json
OData-MaxVersion: 4.01go deeper
Recall that $expand inlines related entities and that options for them go in parentheses after the navigation property, separated by semicolons.
Explain that nested options act per parent, that a nested $filter trims children and never parents, and how $levels recurses through a self-referencing navigation property.
Show that you see how nesting multiplies work, and that expanded collections can carry their own count and next link that a client must handle.
Discuss how deep a public service should let clients expand, and how that limit is communicated, against the round trips a shallower limit pushes onto clients.
## The request, written out A **navigation property** links one entity to related ones — here a `Customer` has an `Orders` collection. `$expand` asks the service to represent the related entities **inline** in the response instead of leaving the client to fetch them separately. Options that shape the related collection go in parentheses after the property name, separated by semicolons: ```http GET /odata/Customers?$filter=City eq 'Lyon' &$select=ID,Name &$expand=Orders($select=ID,OrderDate,Amount;$orderby=OrderDate desc;$top=5;$count=true) OData-MaxVersion: 4.01 ``` (Line breaks are for reading; on the wire this is one URL with spaces percent-encoded.) ## How the nested options are scoped The OData 4.01 Protocol (section 11.2.5.2.1) calls these **expand options**: a semicolon-separated list of system query options, enclosed in parentheses. Their scope is the related collection of each parent, one parent at a time: - `$orderby=OrderDate desc;$top=5` gives **every** customer up to five of its own newest orders; it does not cap the response at five orders in total. - `$filter` inside the parentheses removes related orders from the inline list. The customers themselves are still chosen only by the outer `$filter`, so a customer with no matching orders still appears, with an empty `Orders` array. - `$count=true` inside the expand adds a count of each customer's related orders, written `Orders@count` in 4.01 JSON (`[email protected]` in 4.0). - `$select` inside the expand chooses the order properties; the outer `$select=ID,Name` chooses customer properties. The protocol says expanded navigation properties MUST be returned even if `$select` does not list them, so `Orders` need not appear in the outer `$select`. - A nested `$expand` goes one hop further: `Orders($expand=Items($expand=Product))`. The full list of allowed expand options in 4.01 is `$filter`, `$select`, `$orderby`, `$skip`, `$top`, `$count`, `$search`, `$expand`, `$compute` and `$levels`. `$compute` is new in 4.01; the others were already allowed in 4.0. ## What the response looks like ```json { "@context": "https://api.example.com/odata/$metadata#Customers(ID,Name,Orders(ID,OrderDate,Amount))", "value": [ { "ID": "C007", "Name": "Arnaud Fils", "Orders@count": 23, "Orders": [ { "ID": 9120, "OrderDate": "2026-09-28", "Amount": 410.00 }, { "ID": 9087, "OrderDate": "2026-09-14", "Amount": 95.50 } ] } ] } ``` `Orders@count` is 23 — all of that customer's orders — while only the five newest would be inlined (two are shown). If the service pages an expanded collection, it adds `Orders@nextLink` for that customer. ## Recursion with $levels Some navigation properties are **cyclic**: the target type is the source type, or can be cast to it — an employee's `ReportsTo` is another employee. `$levels` expands such a property recursively: 1. `$levels` takes a positive integer; `$levels=1` is a single expand with no recursion. 2. `Employees?$expand=ReportsTo($levels=3)` returns each employee with the manager, the manager's manager and that manager's manager. 3. The same expand options apply at every level of the hierarchy. 4. Services **MAY** support the symbolic value `max`; one that does MUST break circular dependencies by injecting an entity reference, and a client using `$levels=max` MUST be ready to handle those references. ## Other forms worth knowing - `$expand=Orders/$ref` inlines only references (entity ids) rather than whole orders. - `$expand=Orders/$count` inlines just the number of related orders. - `$expand=*` expands all navigation properties; `*($levels=2)` goes two hops. An explicitly named navigation property takes precedence over the star. - A property MUST NOT appear in more than one expand item. ## Outer and nested options side by side A frequent mistake is to put an option at the wrong level. The same option means something different in each place: | Written as | Applies to | Effect here | |---|---|---| | `Customers?$top=5&$expand=Orders` | the customers | five customers, each with all of its orders | | `Customers?$expand=Orders($top=5)` | each customer's orders | every customer, each with up to five orders | | `Customers?$filter=City eq 'Lyon'` | the customers | only Lyon customers | | `Customers?$expand=Orders($filter=Amount gt 500)` | each customer's orders | every customer, only large orders inline | Selecting parents by a property of their children is a different tool again — a lambda operator such as `any` in the outer `$filter`. ## Why this is where cost appears Each level of nesting multiplies the work: fifty customers with their orders with their items is a tree, not a list. The service decides how deep it will go and whether it will page expanded collections; how it advertises and enforces those limits is a separate question from the syntax shown here.
- In OData 4.01, does `$top=5` inside `$expand=Orders(...)` limit the whole response to five orders?No. Expand options apply to the related collection of each parent separately, so every customer gets up to five of its own orders. Fifty customers can bring back up to 250 orders. Limiting the number of customers is the outer `$top`'s job, and a service that pages an expanded collection signals it with a next link on that customer's `Orders`.
- What does the OData request `Employees?$expand=ReportsTo($levels=3)` return?Each employee with the manager, the manager's manager and that manager's manager inlined, because `$levels` recursively expands a navigation property whose target type matches the source type, and the same expand options apply at every level. `$levels=1` would be a plain single expand. The value `max` is optional for a service to support, and a client using it must handle entity references where a cycle would otherwise occur.
saying these in an interview costs you the question
- A $filter inside $expand also removes the parents that have no matching children
- $top inside $expand caps the total number of related items in the response
- Nested expand options are joined with ampersands like top-level options
- The outer $select must list Orders or the expansion is dropped
- Every OData service must accept $levels=max