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?
answer
- not a header bump
- URLs, methods, model, operations
- $inlinecount, $links, MERGE
- associations and service operations
- two service roots side by side
basics
~20 sAlmost 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.
solid answer
~40 sTreat it as a new protocol, not a version bump. Beyond the JSON changes, v2 URLs break: `$inlinecount=allpages` becomes `$count=true`, `substringof('x',Name)` becomes `contains(Name,'x')`, typed literals such as `datetime'...'` and `guid'...'` become bare literals, and `/$links/Nav` becomes `/Nav/$ref`. Partial updates move from v2's custom `MERGE` method to `PATCH`. In the model, associations and association sets disappear: navigation properties name their `Partner`, and entity sets say where targets live with `NavigationPropertyBinding`. `Edm.DateTime` is gone from the v4 type list, and each service operation must become a function (GET, no side effects) or an action (POST). The 4.x specifications define only versions 4.0 and 4.01, so headers cannot bridge the two. Stand v4 up at its own service root, migrate clients one at a time, and retire v2 once its traffic is gone.
code
http · 10 linesGET /legacy/Parts?$inlinecount=allpages&$filter=substringof('bolt',Name) eq true HTTP/1.1
Host: api.example.com
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
Accept: application/json
GET /v4/Parts?$count=true&$filter=contains(Name,'bolt') HTTP/1.1
Host: api.example.com
OData-MaxVersion: 4.01
Accept: application/jsongo deeper
Recall a few concrete renames: $inlinecount to $count, $links to $ref, MERGE to PATCH, DataServiceVersion to OData-Version.
Explain the model changes: associations replaced by Partner and NavigationPropertyBinding, service operations split into functions and actions, Edm.DateTime remapped.
Show a staged plan: usage inventory, a parallel v4 service root, per-client contract tests, and retirement driven by measured v2 traffic.
Argue when a translating facade for unmovable clients is worth its cost, and how long the organisation can afford to run two protocol surfaces.
## Why this is not a version bump Inside the v4 line, a 4.0 client and a 4.01 service negotiate with `OData-MaxVersion`. Nothing similar spans v2 and v4: the OASIS specifications define only the version values `4.0` and `4.01`, use different headers, and say nothing about answering a `DataServiceVersion` request. Every client-visible surface changed, so a migration means **running two protocols for a while** and moving clients deliberately. ## What breaks, surface by surface | Surface | OData v2 | OData v4 | |---|---|---| | Version headers | `DataServiceVersion`, `MaxDataServiceVersion` | `OData-Version`, `OData-MaxVersion` | | JSON payload | `d` / `results`, `__metadata`, strings for Int64 and Decimal | `value`, `@odata.*` control information, JSON numbers | | Inline count | `$inlinecount=allpages` | `$count=true` | | Substring filter | `substringof('bolt',Name) eq true` | `contains(Name,'bolt')` | | Typed literals | `datetime'2000-12-12T12:00'`, `guid'…'`, `64L`, `2.345M` | `2012-12-03T07:16:23Z`, bare GUID, plain numbers | | Relationship URL | `Categories(1)/$links/Products` | `Categories(1)/Products/$ref` | | Partial update | custom `MERGE` method | `PATCH` (services SHOULD support it) | | Relationships in the model | associations, association sets, navigation properties bound to them | `NavigationProperty` with `Partner` and `ReferentialConstraint`; `NavigationPropertyBinding` on entity sets | | Custom operations | service operations: a `FunctionImport` with an `HttpMethod` | functions (GET, no side effects) and actions (POST), bound or unbound | | Date and time types | `Edm.DateTime` and `Edm.Time`, beside `Edm.DateTimeOffset` | `Edm.DateTimeOffset`, `Edm.Date`, `Edm.TimeOfDay`, `Edm.Duration`; no `Edm.DateTime` | | Atom XML | a first-class format | a 4.0-only format that 4.01 did not update | Batch requests are multipart in both v2 and 4.0, but every part inside now speaks v4 URLs, headers and payloads, and 4.01 adds a JSON batch format, so re-test every client that batches. ## The decisions hidden in the table - **Types.** v2 `Edm.DateTime` carries no offset. Map each column by meaning: to `Edm.DateTimeOffset` when it is an instant, `Edm.Date` when it is a calendar day. Clients that sent `datetime'...'` literals change too. - **Operations.** A v2 service operation is "a simple function" invoked with GET or POST, with its parameters in the query string (`GetProductsByRating?rating=5`). In v4 a **function** MUST return data and MUST have no observable side effects, and it is invoked with GET. Anything that changes state becomes an **action**, invoked with POST. Read each operation's code rather than its `HttpMethod`. - **Updates.** v2's `MERGE` merges only the properties sent. In v4 that is `PATCH`. A v4 `PUT` must replace all structural properties and set missing updatable ones to their defaults, so a client that "upgrades" `MERGE` to `PUT` silently wipes data. - **Relationships.** The model no longer declares a relationship once as an association. Each navigation property names its target type and, for a bidirectional link, its `Partner`, and entity sets bind targets with `NavigationPropertyBinding`. ## Staging the move 1. **Inventory real usage.** From access logs, list the URLs, query options, methods (`MERGE`?), formats (Atom or JSON) and batch use per client. This decides the size of the job. 2. **Pick the target.** 4.01 is the current OASIS Standard, and a conforming 4.01 service must also serve 4.0 clients. Clients that might meet 4.0 services should send `OData-MaxVersion: 4.0` and lower-case, `$`-prefixed query options. 3. **Stand v4 up at a new service root** over the same data, beside the untouched v2 root. Both read and write the same store, so clients can move independently. 4. **Migrate clients one by one,** each with contract tests replaying its real requests against v4: counts, filters, dates, large numbers and partial updates. 5. **Watch v2 traffic per client** and retire the v2 root when it reaches zero, not on a date chosen in advance. ## Failure modes to test before cut-over - **Counts read as strings**: code that parsed the `__count` string now meets a number under a different name. - **Shifted timestamps**: where offset-free `Edm.DateTime` columns were mapped to `Edm.DateTimeOffset`, a client that ignores the offset displays the wrong hour. - **Lost precision**: Int64 identifiers and decimals arrive as JSON numbers; clients that parse into doubles need `IEEE754Compatible=true`. - **Missing links**: minimal metadata omits computable navigation links, so code that followed `__deferred` finds nothing. - **Wiped fields**: a client that replaced `MERGE` with `PUT` resets everything it did not send. ## Where a facade fits When many clients cannot be changed, for example devices or partner integrations, a thin facade can translate v2 requests into v4 calls. It must reproduce v2 shapes exactly, including `/Date()/` strings and string-encoded numbers. That is costly, so keep it for the clients that truly cannot move.
- What replaces v2's associations and association sets in an OData v4 model?The relationship is declared on the navigation properties. Each `NavigationProperty` names its target `Type`, may name a `Partner` navigation property leading back to define a bidirectional relationship, and may carry a `ReferentialConstraint` pairing dependent and principal properties. In the entity container, an entity set's `NavigationPropertyBinding` says which entity set holds the targets. There is no separate association element.
- Would you target OData 4.0 or 4.01 for the new service?4.01, normally. It is the current OASIS Standard, and its minimal conformance level requires conforming to 4.0 minimal conformance. A conforming JSON producer must still generate 4.0 payloads for a 4.0 request, so 4.0 clients keep working. Clients that may also call 4.0-only services should send `OData-MaxVersion: 4.0` and stick to lower-case, `$`-prefixed query options.
- A v2 client "upgrades" its MERGE calls to PUT against the v4 service. What goes wrong?Data loss. v2's `MERGE` updates only the properties sent. A v4 `PUT` must replace all structural properties, setting missing non-key updatable ones to their defaults, and omitting a non-nullable property with no default gives a 400. The v4 equivalent of `MERGE` is `PATCH`, which services SHOULD support as the preferred update.
saying these in an interview costs you the question
- Raising the version header to 4.0 is enough to move a v2 client onto v4.
- OData v4 defines $inlinecount=allpages as an alias for $count=true.
- OData v4 kept associations; Partner is just an optional extra.
- Every v2 service operation simply becomes a v4 action.
- In v4, PUT leaves omitted properties untouched, just like v2's MERGE.
- Edm.DateTime still exists in v4, so date columns need no remapping.