When thinking about persisting a JSON document to DocumentDB today you have to stop and ask yourself this question; “Should I do a Create, or should I do a Replace operation?”
If you do a Create operation and the record already exists you will get an error. Similarly, if you do a Replace operation and the record doesn’t exist you will get an error. So the only way to know the answer to this question is to first do a Read or Query by id operation to look for the document, then decide whether to Create or Replace.
A typical implementation might look like this:
using (DocumentClient client = new DocumentClient(new Uri(endpoint), authKey)) { var docSpec = new { id = "document id", foo = "bar" }; var docExists = client.CreateDocumentQuery(UriFactory.CreateCollectionUri(databaseId, collectionId)) .Where(doc => doc.Id == "document id") .Select(doc => doc.Id) .AsEnumerable() .Any(); //doc exists, so replace with the docSpec if (docExists) { Uri docUri = UriFactory.CreateDocumentUri(databaseId, collectionId, "document id"); await client.ReplaceDocumentAsync(docUri, docSpec); } //doc does not exist, so create a new document using docSpec else { Uri collUri = UriFactory.CreateCollectionUri(databaseId, collectionId); await client.CreateDocumentAsync(collUri, docSpec); } }
This works, most of the time, but is not without problems. The first issue is it costs you additional request units to always ask the database if the record exists first.
Even if you ignore the cost of the additional queries and network round-trips, this code is still a two-step process. There is also still a risk of encountering a race condition. Imagine the scenario where two processes ask the exact same question at the same time; SELECT * FROM c WHERE id = ‘document id’ and both processes get an answer of zero results. Now both processes will proceed to do a Create operation. One process is going to succeed and the other will fail because it tried to insert a record that was already inserted by the other process that won the race.
Today, DocumentDB is happy to announce the addition of support for atomic Upsert operation on the back-end.
Upsert solves these two challenges. With Upsert you don’t need to first ask whether or not the document exists, then decide which operation to perform. The database now makes this decision for you. This not only saves you the additional request unit charges but, because the operation is atomic, it also removes the possibility of a race condition.
Upsert will use the id property of the document and decide whether to create a new document or replace an existing document. It is important to note that DocumentDB does not support partial updates to documents yet. It currently only supports full replacement of documents. Therefore, when a document already exists (matched on id) the full document will be replaced by the contents of the Upsert request.
With Upsert, the code above changes to this:
await client.UpsertDocumentAsync(UriFactory.CreateCollectionUri(databaseId, collectionId), docSpec);
Looking at REST requests under the covers, the above code translates in to a POST on a document resource with a new custom HTTP header x-ms-documentdb-is-upsert set to True. The response from DocumentDB will tell you whether a Create or a Replace was done. If a Create happened, the HTTP response will be StatusCode 201. If a Replace occurred the StatusCode will be a 200.
In addition to making Upsert requests through client-side requests as shown above, you can also make use of the functionality with the JavaScript server-side SDK when building stored procedures and triggers.
To make use of the Upsert feature you need to download the latest .NET, Node.js, Python, or Java SDK based on whichever platform you use.
As with all new features releases, we would love to receive your feedback. Feel free to leave comments below and if you need any help, or have questions, please reach out to us on the developer forums on MSDN or StackovOverflow.
Stay up-to-date on the latest DocumentDB news and features by following us on Twitter @DocumentDB.
Happy document upserting!