Get started with Azure Cosmos DB Partial Document Update

APPLIES TO: NoSQL

This article provides examples that illustrate how to use Partial Document Update with .NET, Java, and Node SDKs. It also describes common errors that you might encounter.

This article links to code samples for the following scenarios:

  • Run a single patch operation
  • Combine multiple patch operations
  • Use conditional patch syntax based on filter predicate
  • Run patch operation as part of a transaction

Prerequisites

Support for Partial Document Update (Patch API) in the Azure Cosmos DB .NET v3 SDK is available starting with version 3.23.0. You can download it from the NuGet Gallery.

Note

Find a complete Partial Document Update sample in the .NET v3 samples repository on GitHub.

  • Run a single patch operation:

    ItemResponse<Product> response = await container.PatchItemAsync<Product>(
        id: "e379aea5-63f5-4623-9a9b-4cd9b33b91d5",
        partitionKey: new PartitionKey("road-bikes"),
        patchOperations: new[] {
            PatchOperation.Replace("/price", 355.45)
        }
    );
    
    Product updated = response.Resource;
    
  • Combine multiple patch operations:

    List<PatchOperation> operations = new ()
    {
        PatchOperation.Add("/color", "silver"),
        PatchOperation.Remove("/used"),
        PatchOperation.Increment("/price", 50.00),
        PatchOperation.Add("/tags/-", "featured-bikes")
    };
    
    ItemResponse<Product> response = await container.PatchItemAsync<Product>(
        id: "e379aea5-63f5-4623-9a9b-4cd9b33b91d5",
        partitionKey: new PartitionKey("road-bikes"),
        patchOperations: operations
    );
    
  • Use conditional patch syntax based on filter predicate:

    PatchItemRequestOptions options = new()
    {
        FilterPredicate = "FROM products p WHERE p.used = false"
    };
    
    List<PatchOperation> operations = new ()
    {
        PatchOperation.Replace($"/price", 100.00),
    };
    
    ItemResponse<Product> response = await container.PatchItemAsync<Product>(
        id: "e379aea5-63f5-4623-9a9b-4cd9b33b91d5",
        partitionKey: new PartitionKey("road-bikes"),
        patchOperations: operations,
        requestOptions: options
    );
    
  • Run patch operation as a part of a transaction:

    TransactionalBatchPatchItemRequestOptions options = new()
    {
        FilterPredicate = "FROM products p WHERE p.used = false"
    };
    
    List<PatchOperation> operations = new ()
    {
        PatchOperation.Add($"/new", true),
        PatchOperation.Remove($"/used")
    };
    
    TransactionalBatch batch = container.CreateTransactionalBatch(
        partitionKey: new PartitionKey("road-bikes")
    );
    batch.PatchItem(
        id: "e379aea5-63f5-4623-9a9b-4cd9b33b91d5",
        patchOperations: operations,
        requestOptions: options
    );
    batch.PatchItem(
        id: "892f609b-8885-44df-a9ed-cce6c0bd2b9e",
        patchOperations: operations,
        requestOptions: options
    );
    
    TransactionalBatchResponse response = await batch.ExecuteAsync();
    bool success = response.IsSuccessStatusCode;
    

Support for server-side programming

Partial Document Update operations can also be executed on the server-side using stored procedures, triggers, and user-defined functions.

this.patchDocument = function (documentLink, patchSpec, options, callback) {
    if (arguments.length < 2) {
        throw new Error(ErrorCodes.BadRequest, sprintf(errorMessages.invalidFunctionCall, 'patchDocument', 2, arguments.length));
    }
    if (patchSpec === null || !(typeof patchSpec === "object" || Array.isArray(patchSpec))) {
        throw new Error(ErrorCodes.BadRequest, errorMessages.patchSpecMustBeObjectOrArray);
    }

    var documentIdTuple = validateDocumentLink(documentLink, false);
    var collectionRid = documentIdTuple.collId;
    var documentResourceIdentifier = documentIdTuple.docId;
    var isNameRouted = documentIdTuple.isNameRouted;

    patchSpec = JSON.stringify(patchSpec);
    var optionsCallbackTuple = validateOptionsAndCallback(options, callback);

    options = optionsCallbackTuple.options;
    callback = optionsCallbackTuple.callback;

    var etag = options.etag || '';
    var indexAction = options.indexAction || '';

    return collectionObjRaw.patch(
        collectionRid,
        documentResourceIdentifier,
        isNameRouted,
        patchSpec,
        etag,
        indexAction,
        function (err, response) {
            if (callback) {
                if (err) {
                    callback(err);
                } else {
                    callback(undefined, JSON.parse(response.body), response.options);
                }
            } else {
                if (err) {
                    throw err;
                }
            }
        }
    );
}; 

Note

Find the definition of validateOptionsAndCallback in the .js DocDbWrapperScript on GitHub.

Sample stored procedure for patch operation:

function patchDemo() {
    var doc = {
        "id": "exampleDoc",
        "fields": {
            "field1": "exampleString",
            "field2": 20,
            "field3": 40
        }
    };
    
    var isAccepted = __.createDocument(__.getSelfLink(), doc, (err, doc) => {
        if (err) {
            throw err;
        }
        else {
            getContext().getResponse().setBody("Example document successfully created.");
            
            var patchSpec = [
                { "op": "add", "path": "/fields/field1", "value": "newExampleString" },
                { "op": "remove", "path": "/fields/field2" },
                { "op": "incr", "path": "/fields/field3", "value": 10 }
            ];
            
            var isAccepted = __.patchDocument(doc._self, patchSpec, (err, doc) => {
                if (err) {
                    throw err;
                }
                else {
                    getContext().getResponse().appendBody(" Example document successfully patched.");
                }
            });
            
            if (!isAccepted) throw new Error("Patch wasn't accepted");
        }
    });

    if (!isAccepted) throw new Error("Create wasn't accepted.");
}

Troubleshooting

Here's some common errors that you might encounter while using this feature:

Error Message Description
Invalid patch request: check syntax of patch specification. The patch operation syntax is invalid. For more information, see the Partial Document Update specification.
Invalid patch request: Can't patch system property SYSTEM_PROPERTY. System-generated properties like _id, _ts, _etag, _rid aren't modifiable using a patch operation. For more information, see Partial Document Update FAQs.
The number of patch operations can't exceed 10. There's a limit of 10 patch operations that can be added in a single patch specification. For more information, see Partial Document Update FAQs.
For Operation(PATCH_OPERATION_INDEX): Index(ARRAY_INDEX) to operate on is out of array bounds. The index of array element to be patched is out of bounds.
For Operation(PATCH_OPERATION_INDEX)): Node(PATH) to be replaced has been removed earlier in the transaction. The path you're trying to patch doesn't exist.
For Operation(PATCH_OPERATION_INDEX): Node(PATH) to be removed is absent. Note: it might also have been removed earlier in the transaction.  The path you're trying to patch doesn't exist.
For Operation(PATCH_OPERATION_INDEX): Node(PATH) to be replaced is absent. The path you're trying to patch doesn't exist.
For Operation(PATCH_OPERATION_INDEX): Node(PATH) isn't a number. Increment operation can only work on integer and float. For more information, see: Supported Operations.
For Operation(PATCH_OPERATION_INDEX): Add Operation can only create a child object of an existing node (array or object) and can't create path recursively, no path found beyond: PATH. Child paths can be added to an object or array node type. Also, to create nth child, n-1th child should be present.
For Operation(PATCH_OPERATION_INDEX): Given Operation can only create a child object of an existing node(array or object) and can't create path recursively, no path found beyond: PATH. Child paths can be added to an object or array node type. Also, to create nth child, n-1th child should be present.

Next steps