3

Technologies: Cosmos DB Emulator, Microsoft.Azure.Cosmos 3.23.0

Goal : Add the first item of an array using Path Add

Initial data :

{
"id": "P1-86",
"ProjectType_Id": "1",
"Description": "AAA"
}

Requested final data :

  {
    "id": "P1-86",
    "ProjectType_Id": "1",
    "Description": "AAA",
    "Products" : [{"Id" : "P-1", "Description" : "My Product"}]
  }

My code :

PatchOperation operation = PatchOperation.Add("/Products/-", command);

TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(project_Id))
        .PatchItem(project_Id, new[] { operation },
        new TransactionalBatchPatchItemRequestOptions
        {
                    EnableContentResponseOnWrite = false,
                    IndexingDirective = IndexingDirective.Exclude
        });

TransactionalBatchResponse batchResponse = await batch.ExecuteAsync();

I get a BadRequest Error.

I try :

"/Products/" => insert the item as an object and not as an array's item

  {
    "id": "P1-86",
    "ProjectType_Id": "1",
    "Description": "AAA",
    "Products" : {"Id" : "P-1", "Description" : "My Product"}
  }

"/Products/0/" => BadRequest

"/Products/0" => BadRequest

"/Products/-" => BadRequest

"/Products/-/" => BadRequest

"/Products//" => BadRequest

According to the doc : https://learn.microsoft.com/en-us/azure/cosmos-db/partial-document-update

Add Add performs one of the following, depending on the target path: If the target path specifies an element that does not exist, it is added. If the target path specifies an element that already exists, its value is replaced. If the target path is a valid array index, a new element will be inserted into the array at the specified index. It shifts existing elements to the right. If the index specified is equal to the length of the array, it will append an element to the array. Instead of specifying an index, you can also use the - character. It will also result in the element being appended to the array.

Note: Specifying an index greater than the array length will result in an error.

Is there a Cosmos Db Emulator limitation ? What's wrong with my code?


Edit 1 :

More informations : If initial data are :

{
"id": "P1-86",
"ProjectType_Id": "1",
"Description": "AAA",
"Products" : []
}

PatchOperation.Add("/Products/-", command); => Work

but in my case the Products array didn't previously exists (migration of data model)

4
  • You have to patch explicit property paths for this to work. Each property requires its own patch operation for ID and Description. You can't send an object Commented Jan 5, 2022 at 17:11
  • If the Products Property already exist, it's work correctly, it didn't work only if the initial Property array (Products) is not defined. Commented Jan 5, 2022 at 17:16
  • I've try "/Products/0/Id" but it doestn't work if Products is not defined initialy (but works fine if it is). It seem we couldn't create a new array if the property doestn't exists... Commented Jan 5, 2022 at 17:20
  • Did you try .add for products first, then subsequent patch operations for the other items within the array? Commented Jan 5, 2022 at 23:18

1 Answer 1

4

Finaly I think that it's not possible to do what i want automaticaly.

From Exception Message when you didn't use Transaction :

BadRequest (400); Substatus: 0; ActivityId: c12f915a-3c4a-4d76-bd7d-ad519bbb8f02; Reason: (Message: {"Errors":["For Operation(1): Add Operation can only create a child object of an existing node(array or object) and cannot create path recursively, no path found beyond: 'Products'.

So I manage to do it by my self, it's not realy cleaver but it's work's.

try
{
PatchOperation operationAdd = PatchOperation.Add("/Products/-", command);
var patchAddResponse = await container.PatchItemAsync<ProjectResponse>(project_Id, new PartitionKey(project_Id), new[] { operationAdd },
new PatchItemRequestOptions
            {
                FilterPredicate = $"from c where IS_DEFINED(c.Products) AND c.id = '{project_Id}'",
                EnableContentResponseOnWrite = false,
                IndexingDirective = IndexingDirective.Exclude
            });

        this.log.LogInformation($"ru : {patchAddResponse.RequestCharge} - AddProductAsync " + command.Id);

        return patchAddResponse.Resource;

    }
    catch (CosmosException ex)
    {
        if (ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed)
        {
            PatchOperation operationCreate = PatchOperation.Add("/Products", new IProduct[] { command });

            var patchCreateResponse = await container.PatchItemAsync<ProjectResponse>(project_Id, new PartitionKey(project_Id), new[] { operationCreate },
           new PatchItemRequestOptions
           {
               EnableContentResponseOnWrite = false,
               IndexingDirective = IndexingDirective.Exclude
           });

            this.log.LogInformation($"ru : {patchCreateResponse.RequestCharge} - AddProductAsync " + command.Id);

            return patchCreateResponse.Resource;
        }

        else
            throw ex;
}

precondition use 1 RU so it's acceptable

Sign up to request clarification or add additional context in comments.

2 Comments

This worked for me, I also extended to help with the situation where the property is there, but NULL. The fix for this situation was to perform a set instead of an add to set the array to the list of 1 item, catching a bad request status code
Let's say you used etag in the attempt to write, and the etag condition failed. That would also give a PreconditionFailed exception. I wonder if it is possible to distinguish between that, and the case when the path doesn't exist.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.