see on GitHub

Patches: How to Perform Single Document Patch Operations

The Patch operation is used to perform partial document updates without having to load, modify, and save a full document. The whole operation is executed on the server-side and is useful as a performance enhancement or for updating denormalized data in entities.

The current page deals with patch operations on single documents.

Syntax

Patching has three possible interfaces: typed session, non-typed session, and operation.

1. Typed Session API

A type safe session interface that allows performing the most patch operations and uses the session facilities to perform multiple operations in one request.

void Increment<T, U>(T entity, Expression<Func<T, U>> path, U valToAdd);

void Increment<T, U>(string id, Expression<Func<T, U>> path, U valToAdd);

void Patch<T, U>(string id, Expression<Func<T, U>> path, U value);

void Patch<T, U>(T entity, Expression<Func<T, U>> path, U value);

void Patch<T, U>(T entity, Expression<Func<T, IEnumerable<U>>> path,
    Expression<Func<JavaScriptArray<U>, object>> arrayAdder);

void Patch<T, U>(string id, Expression<Func<T, IEnumerable<U>>> path,
    Expression<Func<JavaScriptArray<U>, object>> arrayAdder);

2. Non-Typed Session API

A non-typed session interface that exposes the full functionality and uses the session facilities to perform multiple operations in one request.

void Defer(ICommandData command, params ICommandData[] commands);

Parameters

public PatchCommandData(string id, string changeVector, PatchRequest patch, PatchRequest patchIfMissing)

We highly recommend using scripts with parameters. This allows RavenDB to cache scripts and boost performance. Parameters can be accessed in the script through the "args" object, and passed using PatchRequest's "Values" parameter.

public class PatchRequest 
{
    /// <summary>
    /// JavaScript function to use to patch a document
    /// </summary>
    /// <value>The type.</value>
    public string Script { get; set; }

    /// <summary>
    /// Additional arguments passed to JavaScript function from Script.
    /// </summary>
    public Dictionary<string, object> Values { get; set; }
}

3. Operations API

An operations interface that exposes the full functionality and allows performing ad-hoc patch operations without creating a session.

PatchStatus Send(PatchOperation operation);

Parameters

public PatchOperation(string id, string changeVector, PatchRequest patch, PatchRequest patchIfMissing = null, bool skipPatchIfChangeVectorMismatch = false)

Built-in JavaScript Extensions

In addition to ECMAScript 5.1 API, RavenDB introduces a few built-in functions and members:

id(document) function returns the ID of a document
this object Current document (with metadata)
args object Object containing arguments passed to the script
load(id) method Allows document loading, increases the maximum number of allowed steps in a script.
put(id, data, metadata) method Allows document putting, returns generated id
output(...) method Allows debug on your patch, prints passed messages in output tab

Examples

Change field's value

// change FirstName to Robert                
session.Advanced.Patch<Employee, string>(
    "employees/1",
    x => x.FirstName, "Robert");
session.SaveChanges();
// change FirstName to Robert                
session.Advanced.Defer(new PatchCommandData("employees/1", null, new PatchRequest
{
    Script = @"this.FirstName = args.FirstName;",
    Values =
    {
        {"FirstName","Robert" }
    }
}, null));
session.SaveChanges();
// change FirstName to Robert                
store.Operations.Send(new PatchOperation("employees/1", null, new PatchRequest
{
    Script = @"this.FirstName = args.FirstName;",
    Values =
    {
        {"FirstName","Robert" }
    }
}, null));

Changes values of two fields

// change FirstName to Robert and LastName to Carter in single request
// note that in this case, we create single request, but two seperate batch operations
// in order to achieve atomicity, please use the non generic APIs
session.Advanced.Patch<Employee, string>("employees/1", x => x.FirstName, "Robert");
session.Advanced.Patch<Employee, string>("employees/1", x => x.LastName, "Carter");
session.SaveChanges();
// change FirstName to Robert and LastName to Carter in single request
// note that here we do maintain the atomicity of the operation
session.Advanced.Defer(new PatchCommandData("employees/1",null, new PatchRequest
{
    Script = @"
                this.FirstName = args.UserName.FirstName
                this.LastName = args.UserName.LastName",
    Values =
    {
        {"UserName", new { FirstName = "Robert", LastName = "Carter" }}                        
    }
},null));
session.SaveChanges();
// change FirstName to Robert and LastName to Carter in single request
// note that here we do maintain the atomicity of the operation
store.Operations.Send(new PatchOperation("employees/1", null, new PatchRequest
{
    Script = @"
                this.FirstName = args.UserName.FirstName
                this.LastName = args.UserName.LastName",
    Values =
    {
        {"UserName", new { FirstName = "Robert", LastName = "Carter" }}
    }
}, null));

Increment value

// increment Age property value by 10
session.Advanced.Increment<Employee, int>("employees/1", x => x.Age, 10);
session.SaveChanges();
session.Advanced.Defer(new PatchCommandData("employees/1", null, new PatchRequest
{
    Script = @"this.Age += args.AgeToAdd",
    Values =
    {
        {"AgeToAdd", 10 }
    }
},null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("products/1",null, new PatchRequest
{
    Script = @"this.Age += args.AgeToAdd",
    Values =
    {
        {"AgeToAdd", 10 }
    }
}, null));

Add item to array

// add a new comment to Comments
session.Advanced.Patch<BlogPost, BlogComment>("blogposts/1",
    x => x.Comments,
    comments => comments.Add(new BlogComment
    {
        Content = "Lore ipsum",
        Title = "Some title"
    }));
session.SaveChanges();
// add a new comment to Comments
session.Advanced.Defer(new PatchCommandData("blogposts/1", null, new PatchRequest
{
    Script = "this.Comments.push(args.Comment)",
    Values =
    {
        {
            "Comment", new BlogComment
                        {
                            Content = "Lore ipsum",
                            Title = "Some title"
                        }
        }
    }
    
}, null));
session.SaveChanges();
// add a new comment to Comments
store.Operations.Send(new PatchOperation("blogposts/1", null, new PatchRequest
{
    Script = "this.Comments.push(args.Comment)",
    Values =
    {
        {
            "Comment", new BlogComment
                        {
                            Content = "Lore ipsum",
                            Title = "Some title"
                        }
        }
    }

}, null));

Insert item into specific position in array

Inserting item into specific position is supported only by the non-typed APIs
// insert a new comment at position 1 to Comments
session.Advanced.Defer(new PatchCommandData("blogposts/1", null, new PatchRequest
{
    Script = "this.Comments.splice(1,0,args.Comment)",
    Values =
    {
        {"Comment", new BlogComment
                    {
                        Content = "Lore ipsum",
                        Title = "Some title"
                    }
        }
    }
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("blogposts/1",null, new PatchRequest
{
    Script = "this.Comments.splice(1,0,args.Comment)",
    Values =
    {
        {"Comment", new BlogComment
                    {
                        Content = "Lore ipsum",
                        Title = "Some title"
                    }
        }
    }
}, null));

Modify item in specific position in array

Inserting item into specific position is supported only by the non-typed APIs
// modify a comment at position 3 in Comments
session.Advanced.Defer(new PatchCommandData("blogposts/1", null, new PatchRequest
{
    Script = "this.Comments.splice(3,1,args.Comment)",
    Values =
    {
        {"Comment", new BlogComment
                    {
                        Content = "Lore ipsum",
                        Title = "Some title"
                    }
        }
    }
}, null));
session.SaveChanges();
// modify a comment at position 3 in Comments
store.Operations.Send(new PatchOperation("blogposts/1", null, new PatchRequest
{
    Script = "this.Comments.splice(1,0,args.Comment)",
    Values =
    {
        {"Comment", new BlogComment
                    {
                        Content = "Lore ipsum",
                        Title = "Some title"
                    }
        }
    }
}, null));

Filter out items from an array

Filtering items from an array supported only by the non-typed APIs
// filter out all comments of a blogpost which contains the word "wrong" in their contents 
session.Advanced.Defer(new PatchCommandData("blogposts/1", null, new PatchRequest
{
    Script = @"this.Comments = this.Comments.filter(comment=>
                comment.Content.includes(args.TitleToRemove));",
    Values =
    {
        {"TitleToRemove","wrong" }
    }
}, null));
session.SaveChanges();
// filter out all comments of a blogpost which contains the word "wrong" in their contents
store.Operations.Send(new PatchOperation("blogposts/1", null, new PatchRequest
{
    Script = @"this.Comments = this.Comments.filter(comment=>
                comment.Content.includes(args.TitleToRemove));",
    Values =
    {
        {"TitleToRemove","wrong" }
    }
}, null));                

Loading documents in a script

Loading documents supported only by non-typed APIs
// update product names in order, according to loaded product documents
session.Advanced.Defer(new PatchCommandData("orders/1", null, new PatchRequest
{
    Script = @"this.Lines.forEach(line=> { 
                var productDoc = load(line.Product);
                line.ProductName = productDoc.Name;
                });"
}, null));
session.SaveChanges();
// update product names in order, according to loaded product documents
store.Operations.Send(new PatchOperation("blogposts/1", null, new PatchRequest
{
    Script = @"this.Lines.forEach(line=> { 
                var productDoc = load(line.Product);
                line.ProductName = productDoc.Name;
                });"
}, null));

Remove property

Removing property supported only by the non-typed APIs
// rename FirstName to First
session.Advanced.Defer(new PatchCommandData("employees/1", null, new PatchRequest
{
    Script = @" var firstName = this[args.Rename.Old];
                delete this[args.Rename.Old];
                this[args.Rename.New] = firstName",
    Values =
    {
        {
            "Rename", new
            {
                Old = "FirstName",
                New = "Name"
            }
        }
    }
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("employees/1",null, new PatchRequest
{
    Script = @" var firstName = this[args.Rename.Old];
                delete this[args.Rename.Old];
                this[args.Rename.New] = firstName",
    Values =
    {
        {
            "Rename", new
            {
                Old = "FirstName",
                New = "Name"
            }
        }
    }
}, null));

Rename property

Renaming property supported only by the non-typed APIs
// rename FirstName to First
session.Advanced.Defer(new PatchCommandData("employees/1", null, new PatchRequest
{
    Script = @" var firstName = this[args.Rename.Old];
                delete this[args.Rename.Old];
                this[args.Rename.New] = firstName",
    Values =
    {
        {
            "Rename", new
            {
                Old = "FirstName",
                New = "Name"
            }
        }
    }
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("employees/1",null, new PatchRequest
{
    Script = @" var firstName = this[args.Rename.Old];
                delete this[args.Rename.Old];
                this[args.Rename.New] = firstName",
    Values =
    {
        {
            "Rename", new
            {
                Old = "FirstName",
                New = "Name"
            }
        }
    }
}, null));