Partial document updates using the Patching API

The process of document patching allows for modifying a document on the server without having to load it in full and saving it back. This is usually useful for updating denormalized data in entities.

In a normal use case, the client would issue a Load command to the server, deserialize the response into an entity, make changes to that entity, and then send it back for the server serialized. Using the Patching API the client can issue a single Patch command and the server will perform the requested operation on the JSON representation of the document. This can save bandwidth and be faster, but it is not a transactional operation, and as such only the last patching command is actually going to be persisted.

Note

Since this feature involves low-level document manipulation, it is considered to be an expert feature and generally should not be used as a general purpose solution. If you have reached a scenario where you are considering using this, you might want to recheck your data model and see if it can be optimized to prevent usage of the Patching API. The only exception is updating denormalized data, where this approach is valid but not always recommended.

The patching API is exposed through RavenDB's DatabaseCommands, available from the document store object and session.Advanced. A patch command is issued by calling a single function Patch, accepting three parameters: the document key, an array of PatchRequests and an optional Etag:

var comment = new BlogComment
				{
					Title = "Foo",
					Content = "Bar"
				};

documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Add,
					Name = "Comments",
					Value = RavenJObject.FromObject(comment)
				}
		});

The document key is the unique key for the document in the current database, on which this patch command will be performed. Specifying an Etag would ensure changes are only made if no writes were performed since the client acquired the specified Etag.

Following are a description of the PatchRequest object, and the different options available through the Patching API. We will be using a simplistic example of a blog engine, using these classes:

public class BlogPost
{
	public string Id { get; set; }
	public string Title { get; set; }
	public string Category { get; set; }
	public string Content { get; set; }
	public DateTime PublishedAt { get; set; }
	public string[] Tags { get; set; }
	public BlogComment[] Comments { get; set; }
}

public class BlogComment
{
	public string Title { get; set; }
	public string Content { get; set; }
}

The PatchRequest object

When creating a PatchRequest object to be used in a patch command, at least 2 properties have to be specified: Name and Type.

Given the object graph stored under a given key, Name is the path from the root to a property (or properties) within that object graph. The syntax is similar to how XPath operates on XML, only more simplistic.

Type is what defines the patch command. It can be one of the following:

  • Set - Set a property
  • Unset - Unset (remove) a property
  • Inc - Increment a property by a specified value
  • Rename - Rename a property
  • Copy - Copy a property value to another property
  • Modify - Modify a property value by providing a nested set of patch operation
  • Add - Add an item to an array
  • Insert - Insert an item to an array at a specified position
  • Remove - Remove an item from an array at a specified position

Performing simple updates

A property in a stored document is a field from an entity. To change it's value using the Patching API, provide its path in Name, and initialize Type with PatchCommandType.Set. Then, serialize the object you want to save into that property, and pass it as Value.

The new value you set can be anything: a native type, an object, or a collection of entities. You can use RavenJObject.FromObject(object) to easily serialize it:

// Setting a native type value
documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Set,
					Name = "Title",
					Value = RavenJObject.FromObject("New title")
				}
		});

// Setting an object as a property value
documentStore.DatabaseCommands.Patch(
	"blogposts/4321",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Set,
					Name = "Author",
					Value = RavenJObject.FromObject(
						new BlogAuthor
							{
								Name = "Itamar",
								ImageUrl = "/author_images/itamar.jpg"
							})
				}
		});

Removing a property is done by simply passing PatchCommandType.Unset as Type.

To rename a property, or copy it's value to another property, specify the new path as the Value:

// This is how you rename a property; copying works
// exactly the same, but with Type = PatchCommandType.Copy
documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Rename,
					Name = "Comments",
					Value = new RavenJValue("cmts")
				}
		});

Numeric values used as counters can be incremented or decremented, without worrying about their actual value. Use positive values to increment, and negative values to have it decremented:

// Assuming we have a Views counter in our entity
documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Inc,
					Name = "Views",
					Value = new RavenJValue(1)
				}
		});

Conditional updates

If PrevVal is set, it's value will be compared against the current value of the property to verify a change isn't overwriting new values. If the value is null, the operation is always successful.

Working with arrays

Any collection in your entity will be serialized into an array in the resulting JSON document. You can perform collection-specific operations on it easily, by using the Position property:

// Append a new comment; Insert operation is supported
// as well, by using PatchCommandType.Add and
// specifying a Position to insert at
documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Add,
					Name = "Comments",
					Value =
						RavenJObject.FromObject(new BlogComment
						                        	{Content = "FooBar"})
				}
		});

// Remove the first comment
documentStore.DatabaseCommands.Patch(
	"blogposts/1234",
	new[]
		{
			new PatchRequest
				{
					Type = PatchCommandType.Remove,
					Name = "Comments",
					Position = 0
				}
		});

Being a JSON object, you can treat the entire array as value like shown above. Sometimes, however, you want to access certain items in the array

Working with nested operations

The nested operations are only valid of the 'Type' is PatchCommandType.Modify.
If we want to change all items in a collection we could do that by setting the AllPositions property to 'true'

Here are a few examples of nested operations:

Set value in a nested element:

var addToPatchedDoc = new JsonPatcher(doc).Apply(
	new[]
{
	new PatchRequest
	{
		Type = PatchCommandType.Modify,
		Name = "user",
		Nested = new[]
		{
			new PatchRequest {Type = PatchCommandType.Set, Name = "name", Value = new RavenJValue("rahien")},
		}
	},
});

Remove value in a nested element:

var removeFromPatchedDoc = new JsonPatcher(doc).Apply(
new[]
{
	new PatchRequest
	{
		Type = PatchCommandType.Modify,
		Name = "user",
		PrevVal = RavenJObject.Parse(@"{ ""name"": ""ayende"", ""id"": 13}"),
		Nested = new[]
		{
			new PatchRequest {Type = PatchCommandType.Unset, Name = "name" },
		}
	},
});

Concurrency

If we wanted to we could run several batch operations in parallel, but we will not be able to set which one will end first.