9.3 Data Synchronization with Change Tracking & Alternate Keys
Key Takeaways
- Change tracking lets an external system retrieve only the records created, updated, or deleted since its last sync, using a token instead of re-pulling the entire table every time.
- Change tracking is enabled per table through the 'Track changes' advanced option (or EntityMetadata.ChangeTrackingEnabled) and, once enabled, cannot be disabled.
- The Web API returns change data through the Prefer: odata.track-changes header and an @odata.deltaLink containing the token for the next request; the SDK uses RetrieveEntityChangesRequest and a DataToken.
- Unprocessed change tokens expire after a default of seven days (ExpireChangeTrackingInDays), after which a client must perform a full resync.
- Alternate keys let up to 16 columns from an external system's own identifier uniquely identify a Dataverse row, avoiding the need to store or look up the platform-generated GUID.
Publishing individual events in near real time, as covered in the previous two sections, isn't the only integration shape PL-400 tests. Many integrations are batch or periodic — an external ERP, data warehouse, or mobile app periodically asks Dataverse "what's changed since I last asked?" That pattern depends on two developer features: change tracking, which answers the "what changed" question efficiently, and alternate keys, which let the external system identify Dataverse records using its own identifiers instead of the platform's GUIDs.
Change Tracking: Syncing Only What Changed
Without change tracking, a synchronization job has to pull the entire table every time and compare it against its own copy to work out what's new — expensive and slow as tables grow. Change tracking solves this by having Dataverse hand back a token representing a point in time; on the next request, the client passes that token back and receives only the records that changed since then.
Enabling it: Change tracking is a per-table property. In the maker portal, open the table's Advanced options and turn on Track changes; programmatically, set EntityMetadata.ChangeTrackingEnabled to true. Not every table is eligible — check EntityMetadata.CanChangeTrackingBeEnabled before trying. Once enabled for a table, it cannot be disabled, which is worth remembering before flipping it on for a table with heavy write volume purely for a one-off integration.
Using it via the Web API: send a GET request with a Prefer: odata.track-changes header. The response includes an @odata.deltaLink — a URL containing an opaque $deltatoken — that you store and use as the URL for your next request instead of the original query. That follow-up response contains only new/updated rows plus deleted rows flagged with "reason": "deleted". Standard query options like $filter, $orderby, $expand, and $top are not supported on a tracked-changes request.
Using it via the SDK: the RetrieveEntityChangesRequest message returns a RetrieveEntityChangesResponse whose EntityChanges property carries a DataToken (save it for the next call), a collection of NewOrUpdatedItem/RemovedOrDeletedItem entries, and MoreRecords/PagingCookie for paging through large result sets. The very first call — made with no token — returns every existing record as "new," which is how you perform the initial full load before switching to incremental sync.
Constraints to know:
- Only one table can be tracked per
RetrieveEntityChangescall. - Unprocessed change data expires after a default of seven days, controlled by the organization's
ExpireChangeTrackingInDayssetting — wait longer than that between syncs and the token is no longer valid, forcing a full resync. - New/updated records are returned before deleted records, and large result sets page at 5,000 records per page.
Alternate Keys: Identifying Records Without the GUID
An external system almost never knows a Dataverse record's primary-key GUID — it knows its own identifier (a legacy ERP customer number, a product SKU, a partner system's ID). An alternate key lets you designate one or more columns on a table as a secondary unique identifier that Dataverse enforces with a database index, so external callers can address a record by that business key instead.
Key facts for the exam:
| Constraint | Value |
|---|---|
| Max columns per alternate key | 16 (also capped at 900 bytes total, a SQL index limit) |
| Max alternate key definitions per table | 10 |
| Supported column types | Decimal Number, Whole Number, Single Line of Text, Date Time, Lookup, Option Set |
| Field-level security | Not allowed on key columns |
| Virtual tables | Alternate keys are not supported, because uniqueness can't be enforced on data that lives in another system |
Keys are created through the table designer or programmatically with EntityKeyMetadata and CreateEntityKey; because building the underlying index can take time on a table with existing data, index creation runs as a background job you can monitor through EntityKeyIndexStatus (Pending, In Progress, Active, Failed). One practical gotcha: if a key column's value contains characters like / < > * % & : \ ? +, retrieve, update, and upsert operations that reference the key won't work — pick key columns whose values are guaranteed clean if they're going to be used for integration.
Once a table has an alternate key, both the Web API (accounts(accountnumber='A123')) and the SDK (Entity.KeyAttributes, a KeyAttributeCollection) can reference that record without ever knowing its GUID. Combined with change tracking's ability to say "here's what changed," alternate keys give the external system everything it needs to identify which Dataverse record a changed external record corresponds to — the missing piece that the next section, upsert-based synchronization, ties together into a single write operation.
An external mobile app needs to periodically retrieve only the Dataverse records that changed since its last sync, without re-downloading the entire table each time. Which feature should the integration use?
A table has change tracking enabled, but the integration job hasn't run in 10 days. What happens when it tries to use its saved token to retrieve changes, assuming the default configuration?