How does an OData 4.01 client use the track-changes preference and delta links to keep a local copy of a collection in sync?
answer
- asked for on the first request
- replaces the next link at the end
- opaque, do not append options
- removed, with a reason
- 410 means start over
basics
~20 sSend Prefer: odata.track-changes on the initial query; its last page carries a delta link instead of a next link. GETting that link, with the preference again, returns only added, changed and removed entities and a fresh delta link; 410 Gone means reload.
solid answer
~50 sThe client sends `Prefer: odata.track-changes` (named `track-changes` in 4.01) on the **initial** request; a service that tracks changes confirms it with `Preference-Applied` on the first page and puts a **delta link** on the **last** page in place of the next link. The link is opaque and encodes the defining query and a starting point, so the client GETs it as-is - no extra system query options, though `/$count` may be appended. The delta response lists added and changed entities, **removed** entities (4.01: `@removed` with reason `deleted` or `changed`, the latter for entities that left the result), and added or deleted links for expanded relationships, and, if the client repeated the preference on that request, its last page carries a new delta link. If the link has expired the service answers **410 Gone**, and should give a `Location` for refetching the whole set.
code
json · 9 lines{
"@context": "https://crm.example.com/service/$metadata#Customers/$delta",
"value": [
{ "@id": "Customers('BOTTM')", "ContactName": "Susan Halvenstern" },
{ "@removed": { "reason": "changed" }, "@id": "Customers('ALFKI')" },
{ "@removed": { "reason": "deleted" }, "@id": "Customers('ANTON')" }
],
"@deltaLink": "Customers?$deltatoken=8015"
}go deeper
Know that OData can return a delta link so a client fetches only what changed since its last read, and that it appears at the end of the results.
Explain the flow: preference on the first request, Preference-Applied, delta link on the last page, GET it unchanged with the preference repeated, store the new link each time.
Handle the hard cases: removed with reason changed versus deleted, idempotent application of possibly unchanged entities, ordered multi-page deltas, and recovery after 410 Gone.
Weigh offering change tracking against what it costs the service - retained change history, link lifetimes, filtered tracking - and decide how long links must stay valid.
## The problem delta links solve A client that mirrors a collection - an offline list, a cache, a downstream copy - cannot afford to re-read everything on every sync. OData's **change tracking** lets it ask once for the full result and afterwards only for what changed. A service advertises support with the `Capabilities.ChangeTracking` annotation on the entity set. ## Starting to track 1. The client sends the **defining query** - the GET whose results it wants to follow - with the preference: ```http GET https://crm.example.com/service/Customers Prefer: odata.track-changes ``` 2. If the service tracks changes for it, the first page carries `Preference-Applied` with the preference. 3. For a paged result the preference must be on the **initial** request; services ignore it on a next link. 4. The **delta link** (`@deltaLink`, or `@odata.deltaLink` in 4.0 payloads) appears only on the **last page**, in place of the next link. A page never has both. ## The delta link A delta link is opaque and service-generated. It encodes the set being tracked and the point to start from; services may use the reserved `$deltatoken` query option inside it. Rules a client must respect: - **Do not edit it.** The client must not append system query options; filtering, projection and expansion come from the defining query. Appending `/$count` is allowed and returns the number of pending changes. - **Keep the language.** Clients should send the same `Accept-Language` as the defining query. - It never encodes a client `$top` or `$skip`, because a page window has no meaning for changes. - `metadata=none` is not valid on a delta request. ## Reading a delta response | Item in `value` | Meaning | 4.01 shape | |---|---|---| | Added or changed entity | apply these properties | entity with `@id` (or its keys) and changed properties | | Deleted entity, reason `deleted` | destroyed - remove locally | `"@removed": {"reason": "deleted"}` plus `@id` or the keys | | Deleted entity, reason `changed` | still exists but left the result | `"@removed": {"reason": "changed"}` | | Added link | a relationship in an expanded path appeared | context `#Customers/$link`, `source`, `relationship`, `target` | | Deleted link | a relationship disappeared | context `#Customers/$deletedLink` | - An entity counts as **changed** when a structural property changes; a change to a related entity is not a change to its parent. - If the defining query had a filter, entities that no longer match come back as **removed**, and entities that now match come back as **added**. - Services should send only changed entities but may include unchanged ones, so the client's apply step must tolerate entities that did not really change. - Changes may span pages and must be ordered so that applying them in sequence gives a consistent result. When requested, the last page carries the **next delta link**; if nothing changed, the response is an empty collection with a delta link. - To keep tracking, the client sends the preference on its first request to the delta link, not on later pages. ## Expansions and filters If the defining query expanded related entities, the delta also reports changes to those entities and **added or deleted links** to them; a client that cares only about membership can expand `/$ref` and receive link changes without content changes. A navigation property listed only in a projection does not widen what is tracked. Filtering is not guaranteed: the `ChangeTracking` annotation's `FilterableProperties` names the properties a tracked query may filter on, and when it is absent a client cannot assume filtering works together with change tracking. ## When the link expires A delta link is not valid forever. If the service can no longer produce changes from that point, it answers **410 Gone** and should include a `Location` header with a URL for refetching the entire set. Retrying is pointless; the client discards its local state and starts again with a fresh tracked query. ## Version notes OData 4.0 names the preference `odata.track-changes`; 4.01 names it `track-changes`. Services supporting it should also accept the 4.0 name, and clients should send `odata.track-changes` to work with 4.0 services. The deleted-entity shape differs too: a 4.0 payload marks it with the context fragment `#Customers/$deletedEntity` and plain `id` and `reason` properties, while 4.01 uses the `@removed` control information.
- A tracked query filters customers by country, and one customer moves abroad. What does the next delta response contain?The customer appears as a removed entity with reason `changed`: it still exists but no longer matches the defining query. Reason `deleted` is reserved for entities that were destroyed. Conversely, a customer that starts matching the filter appears as an added entity.
- Why must the client not append $top or $filter to a delta link?The delta link is opaque and already encodes the defining query and the starting point; the specification forbids appending system query options. Narrowing it would change which changes the token stands for. The only allowed addition is `/$count` on the path, which returns the number of pending changes.
- What should a client do when a stored delta link returns 410 Gone?Treat its local copy as unsynchronisable from that point: the service can no longer produce the changes. It should refetch the full set - from the `Location` URL if the service supplied one - with the track-changes preference again, and replace its local state.
A delta link works like the closing reference on a bank statement: next time you show it, the bank lists only transactions since then. If the bank has purged that period, it cannot list the gap, and you must ask for the full account history again.
saying these in an interview costs you the question
- You can start tracking midway by sending track-changes on a next link.
- Every page carries both a next link and a delta link.
- A delta link can be narrowed by appending $filter or $top to it.
- Entities that stop matching the filter simply never appear in the delta.
- A 410 Gone from a delta link is transient and should be retried with backoff.
- A delta response never contains entities that did not change.