Perform conditional operations using the Web API

Microsoft Dataverse provides support for a set of conditional operations that rely upon the standard HTTP resource versioning mechanism known as ETags.

ETags

The HTTP protocol defines an entity tag, or ETag for short, for identifying specific versions of a resource. ETags are opaque identifiers whose exact values are implementation dependent. ETag values occur in two varieties: strong and weak validation. Strong validation indicates that a unique resource, identified by a specific URI, are identical on the binary level if its corresponding ETag value is unchanged. Weak validation only guarantees that the resource representation is semantically equivalent for the same ETag value.

Dataverse generates a weakly validating @odata.etag property for every entity instance, and this property is automatically returned with each retrieved entity record. For more information, see Retrieve a table row using the Web API.

If-Match and If-None-Match headers

Use If-Match and If-None-Match headers with ETag values to check whether the current version of a resource matches the one last retrieved, matches any previous version, or matches no version. These comparisons form the basis of conditional operation support. Dataverse provides ETags to support conditional retrievals, optimistic concurrency, and limited upsert operations.

Warning

Client code should not give any meaning to the specific value of an ETag, nor to any apparent relationship between ETags beyond equality or inequality. For example, an ETag value for a more recent version of a resource is not guaranteed to be greater than the ETag value for an earlier version. Also, the algorithm used to generate new ETag values may change without notice between releases of a service.

Conditional retrievals

Etags enable you to optimize record retrievals whenever you access the same record multiple times. If you previously retrieved a record, you can pass the ETag value with the If-None-Match header to request data to be retrieved only if it changed since the last time it was retrieved. If the data changed, the request returns an HTTP status of 200 OK with the latest data in the body of the request. If the data is unchanged, the HTTP status code 304 Not Modified is returned to indicate that the record hasn't been modified.

The following example message pair returns data for an account record with the accountid equal to 00000000-0000-0000-0000-000000000001 when the data hasn't changed since it was last retrieved when the Etag value was W/"468026"

Request:

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?$select=accountcategorycode,accountnumber,creditonhold,createdon,numberofemployees,name,revenue   HTTP/1.1  
Accept: application/json  
OData-MaxVersion: 4.0  
OData-Version: 4.0  
If-None-Match: W/"468026"  

Response:

HTTP/1.1 304 Not Modified  
Content-Type: application/json; odata.metadata=minimal  
OData-Version: 4.0  

The following sections describe limitations to using conditional retrievals.

Table must have optimistic concurrency enabled

Check if an table has optimistic concurrency enabled using the following Web API request. Tables that have optimistic concurrency enabled have EntityMetadata.IsOptimisticConcurrencyEnabled property set to true.

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='<Entity Logical Name>')?$select=IsOptimisticConcurrencyEnabled

Query must not include $expand

The Etag can only detect if the single record that is being retrieved changed. When you use $expand in your query, more records might be returned and it isn't possible to detect whether or not any of those records changed. If the query includes $expand, 304 Not Modified is never returned.

Query must not include annotations

When the Prefer: odata.include-annotations header is included with a GET request, 304 Not Modified is never returned. The values of annotations can refer to values from related records. These records might have changed and this change couldn't be detected, so it would be incorrect to indicate that nothing changed.

Limit upsert operations

An upsert ordinarily operates by creating a record if it doesn't exist; otherwise, it updates an existing record. However, ETags can be used to further constrain upserts to either prevent creates or to prevent updates.

Prevent create in upsert

If you're updating data and there's some possibility that the record was deleted intentionally, you won't want to re-create the record. To prevent this, add an If-Match header to the request with a value of *.

Request:

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1  
Content-Type: application/json  
OData-MaxVersion: 4.0  
OData-Version: 4.0  
If-Match: *  
  
{  
    "name": "Updated Sample Account ",  
    "creditonhold": true,  
    "address1_latitude": 47.639583,  
    "description": "This is the updated description of the sample account",  
    "revenue": 6000000,  
    "accountcategorycode": 2  
}  

Response:

If the record is found, you get a normal response with status 204 No Content. When the record isn't found, you get the following response with status 404 Not Found.

HTTP/1.1 404 Not Found  
OData-Version: 4.0  
Content-Type: application/json; odata.metadata=minimal  
  
{  
 "error": {  
  "code": "",  
  "message": "account With Id = 00000000-0000-0000-0000-000000000001 Does Not Exist"
 }  
}  

Prevent update in upsert

If you're inserting data, there's some possibility that a record with the same id value already exists in the system and you might not want to update it. To prevent this, add an If-None-Match header to the request with a value of "*".

Request:

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1  
Content-Type: application/json  
OData-MaxVersion: 4.0  
OData-Version: 4.0  
If-None-Match: *  
  
{  
    "name": "Updated Sample Account ",  
    "creditonhold": true,  
    "address1_latitude": 47.639583,  
    "description": "This is the updated description of the sample account",  
    "revenue": 6000000,  
    "accountcategorycode": 2  
}  

Response:
If the record isn't found, you'll get a normal response with status 204 No Content. When the record is found, you get the following response with status 412 Precondition Failed.

HTTP/1.1 412 Precondition Failed  
OData-Version: 4.0  
Content-Type: application/json; odata.metadata=minimal  
  
{  
  "error":{  
   "code":"",  
   "message":"A record with matching key values already exists."
  }  
}  

Apply optimistic concurrency

You can use optimistic concurrency to detect whether a record was modified since it was last retrieved. If the record you intend to update or delete has changed on the server since you retrieved it, you might not want to complete the update or delete operation. By applying the pattern shown here you can detect this situation, retrieve the most recent version of the record, and apply any necessary criteria to reevaluate whether to try the operation again.

Apply optimistic concurrency on delete

The following delete request for an account with accountid of00000000-0000-0000-0000-000000000001 fails because the ETag value sent with the If-Match header is different from the current value. If the value matched, a 204 No Content status is expected.

Request:

DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1  
If-Match: W/"470867"  
Accept: application/json  
OData-MaxVersion: 4.0  
OData-Version: 4.0  

Response:

HTTP/1.1 412 Precondition Failed  
Content-Type: application/json; odata.metadata=minimal  
OData-Version: 4.0  
  
{  
  "error":{  
    "code":"","message":"The version of the existing record doesn't match the RowVersion property provided." 
  }  
}  

Apply optimistic concurrency on update

The following update request for an account with accountid of 00000000-0000-0000-0000-000000000001 fails because the ETag value sent with the If-Match header is different from the current value. If the value matched, a 204 No Content status is expected.

Request:

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1  
If-Match: W/"470867"  
Accept: application/json  
OData-MaxVersion: 4.0  
OData-Version: 4.0  
  
{"name":"Updated Account Name"}  

Response:

HTTP/1.1 412 Precondition Failed  
Content-Type: application/json; odata.metadata=minimal  
OData-Version: 4.0  
  
{  
  "error":{  
    "code":"","message":"The version of the existing record doesn't match the RowVersion property provided."
  }  
}  

See also

Web API Conditional Operations Sample (C#)
Web API Conditional Operations Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table row using the Web API
Retrieve a table row using the Web API
Update and delete table rows using the Web API
Associate and disassociate table rows using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API