Single Document Patch Operations
-
The Patch operation is used to perform partial document updates with one trip to the server, instead of loading, modifying, and saving 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. -
Since the operation is executed in a single request to the database,
the patch command is performed in a single write transaction. -
The current page covers patch operations on single documents.
-
Patching has three possible interfaces: Session API, Session API using Defer, and Operations API.
-
Patching can be done from the client API as well as in the studio.
In this page:
- API overview
-
Examples
- Change value of single field
- Change values of two fields
- Increment value
- Add or increment
- Add or patch
- Add or patch to an existing array
- Add item to array
- Insert item into specific position in array
- Modify item in specific position in array
- Remove items from array
- Loading documents in a script
- Remove property
- Rename property
- Add document
- Clone document
- Increment counter
- Delete counter
- Get counter
API Overview
Session API
A type-safe session interface that allows performing the most common patch operations.
The patch request will be sent to the server only when calling SaveChanges
.
This way it's possible to perform multiple operations in one request to the server.
Increment field value
Session.Advanced.Increment
void Increment<T, U>(T entity, Expression<Func<T, U>> fieldPath, U delta);
void Increment<T, U>(string id, Expression<Func<T, U>> fieldPath, U delta);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
U | Type |
Field type, must be of numeric type or a string of char for string concatenation |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation, this way, the session can track down the entity's ID |
entity id | string |
Entity ID on which the operation should be performed. |
fieldPath | Expression<Func<T, U>> |
Lambda describing the path to the field. |
delta | U |
Value to be added. |
- Note how numbers are handled with the JavaScript engine in RavenDB.
Session.Advanced.AddOrIncrement
void AddOrIncrement<T, TU>(string id, T entity, Expression<Func<T, TU>> path, TU valToAdd);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
TU | Type |
Field type, must be of numeric type or a string of char for string concatenation |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation, this way, the session can track down the entity's ID |
entity id | string |
Entity ID on which the operation should be performed. |
path | Expression<Func<T, TU>> |
Lambda describing the path to the field. |
valToAdd | U |
Value to be added. |
Set field value
Session.Advanced.Patch
void Patch<T, U>(string id, Expression<Func<T, U>> fieldPath, U value);
void Patch<T, U>(T entity, Expression<Func<T, U>> fieldPath, U value);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
U | Type |
Field type |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation. This way the session can track down the entity's ID. |
entity id | string |
Entity ID on which the operation should be performed. |
fieldPath | Expression<Func<T, U>> |
Lambda describing the path to the field. |
delta | U |
Value to set. |
Session.Advanced.AddOrPatch
void AddOrPatch<T, TU>(string id, T entity, Expression<Func<T, TU>> path, TU value);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
TU | Type |
Field type |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation. This way the session can track down the entity's ID. |
entity id | string |
Entity ID on which the operation should be performed. |
fieldPath | Expression<Func<T, TU>> |
Lambda describing the path to the field. |
value | U |
Value to set. |
Array manipulation
Session.Advanced.Patch
void Patch<T, U>(T entity, Expression<Func<T, IEnumerable<U>>> fieldPath,
Expression<Func<JavaScriptArray<U>, object>> arrayModificationLambda);
void Patch<T, U>(string id, Expression<Func<T, IEnumerable<U>>> fieldPath,
Expression<Func<JavaScriptArray<U>, object>> arrayModificationLambda);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
U | Type |
Field type |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation. This way the session can track down the entity's ID. |
entity id | string |
Entity ID on which the operation should be performed. |
fieldPath | Expression<Func<T, U>> |
Lambda describing the path to the field. |
arrayModificationLambda | Expression<Func<JavaScriptArray<U>, object>> |
Lambda that modifies the array, see JavaScriptArray below. |
Session.Advanced.AddOrPatch
void AddOrPatch<T, TU>(string id, T entity, Expression<Func<T, List<TU>>> path,
Expression<Func<JavaScriptArray<TU>, object>> arrayAdder);
Parameters | Type | Description |
---|---|---|
T | Type |
Entity type |
TU | Type |
Field type |
entity | T |
Entity on which the operation should be performed. The entity should be one that was returned by the current session in a Load or Query operation. This way the session can track down the entity's ID. |
entity id | string |
Entity ID on which the operation should be performed. |
path | Expression<Func<T, TU>> |
Lambda describing the path to the field. |
Expression<Func |
Expression<Func<JavaScriptArray<TU>, object>> |
Lambda that modifies the array, see JavaScriptArray below. |
arrayAdder | Add() |
Values to add to array. |
JavaScriptArray
JavaScriptArray
allows building lambdas representing array manipulations for patches.
Method Signature | Return Type | Description |
---|---|---|
Put(T item) | JavaScriptArray |
Allows adding item to an array. |
Put(params T[] items) | JavaScriptArray |
Items to be added to the array. |
RemoveAt(int index) | JavaScriptArray |
Removes item in position index in array. |
RemoveAll(Func<T, bool> predicate) | JavaScriptArray |
Removes all the items in the array that satisfy the given predicate. |
Session API using Defer
The non-typed Session API for patches uses the Session.Advanced.Defer
function which allows registering one or more commands.
One of the possible commands is the PatchCommandData
, describing single document patch command.
The patch request will be sent to the server only when calling SaveChanges
, this way it's possible to perform multiple operations in one request to the server.
Session.Advanced.Defer
void Defer(ICommandData[] commands);
PatchCommandData
Constructor | Type | Description |
---|---|---|
id | string |
ID of the document to be patched. |
changeVector | string |
[Can be null] Change vector of the document to be patched, used to verify that the document was not changed before the patch reached it. |
patch | PatchRequest |
Patch request to be performed on the document. |
patchIfMissing | PatchRequest |
[Can be null] Patch request to be performed if no document with the given ID was found. |
PatchRequest
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.
Property | Type | Description |
---|---|---|
Script | string |
The patching script, written in JavaScript. |
Values | Dictionary<string, object> |
Parameters to be passed to the script. The parameters can be accessed using the '$' prefix. Parameter starting with a '$' will be used as is, without further concatenation. |
Operations API
An operations interface that exposes the full functionality and allows performing ad-hoc patch operations without creating a session.
Raven.Client.Documents.Operations.Send
Raven.Client.Documents.Operations.SendAsync
PatchStatus Send(PatchOperation operation);
Task<PatchStatus> SendAsync(PatchOperation operation,
SessionInfo sessionInfo = null,
CancellationToken token = default(CancellationToken));
PatchOperation
Constructor | Type | Description |
---|---|---|
id | string |
ID of the document to be patched. |
changeVector | string |
Change vector of the document to be patched. Used to verify that the document was not modified before the patch reached it. Can be null . |
patch | PatchRequest |
Patch request to perform on the document. |
patchIfMissing | PatchRequest |
Patch request to perform if the specified document is not found. Will run only if no changeVector was passed.Can be null . |
skipPatchIfChangeVectorMismatch | bool |
true - do not patch if the document has been modified.false (Default) - execute the patch even if document has been modified.An exception is thrown if: this param is false + changeVector has value + document with that ID and change vector was not found. |
List of script methods
This is a list of a few of the javascript methods that can be used in patch scripts.
See the more comprehensive list at Knowledge Base: JavaScript Engine.
Method | Arguments | Description |
---|---|---|
load | string or string[] |
Loads one or more documents into the context of the script by their document IDs |
loadPath | A document and a path to an ID within that document | Loads a related document by the path to its ID |
del | Document ID; change vector | Delete the given document by its ID. If you add the expected change vector and the document's current change vector does not match, the document will not be deleted. |
put | Document ID; document; change vector | Create or overwrite a document with a specified ID and entity. If you try to overwrite an existing document and pass the expected change vector, the put will fail if the specified change vector does not match the document's current change vector. |
cmpxchg | Key | Load a compare exchange value into the context of the script using its key |
getMetadata | Document | Returns the document's metadata |
id | Document | Returns the document's ID |
lastModified | Document | Returns the DateTime of the most recent modification made to the given document |
counter | Document; counter name | Returns the value of the specified counter in the specified document |
counterRaw | Document; counter name | Returns the specified counter in the specified document as a key-value pair |
incrementCounter | Document; counter name | Increases the value of the counter by one |
deleteCounter | Document; counter name | Deletes the counter |
spatial.distance | Two points by latitude and longitude; spatial units | Find the distance between to points on the earth |
timeseries | Document; the time series' name | Returns the specified time series object |
Examples
Change value of single field
// 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(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.FirstName = args.FirstName;",
Values =
{
{"FirstName", "Robert"}
}
},
patchIfMissing: null));
session.SaveChanges();
// change FirstName to Robert
store.Operations.Send(new PatchOperation(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.FirstName = args.FirstName;",
Values =
{
{"FirstName", "Robert"}
}
},
patchIfMissing: null));
Change values of two fields
// Modify FirstName to Robert and LastName to Carter in single request
// ===================================================================
// The two Patch operations below are sent via 'SaveChanges()' which complete transactionally,
// as this call generates a single HTTP request to the database.
// Either both will succeed or both will be rolled back since they are applied within the same transaction.
// However, on the server side, the two Patch operations are still executed separately.
// To achieve atomicity at the level of a single server-side operation, use 'Defer' or the operations syntax.
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(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"
this.FirstName = args.UserName.FirstName;
this.LastName = args.UserName.LastName;",
Values =
{
{
"UserName", new
{
FirstName = "Robert",
LastName = "Carter"
}
}
}
},
patchIfMissing: 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(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"
this.FirstName = args.UserName.FirstName;
this.LastName = args.UserName.LastName;",
Values =
{
{
"UserName", new
{
FirstName = "Robert",
LastName = "Carter"
}
}
}
}, patchIfMissing: null));
Increment value
// increment UnitsInStock property value by 10
session.Advanced.Increment<Product, int>("products/1-A", x => x.UnitsInStock, 10);
session.SaveChanges();
session.Advanced.Defer(new PatchCommandData(
id: "products/1-A",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.UnitsInStock += args.UnitsToAdd;",
Values =
{
{"UnitsToAdd", 10}
}
},
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "products/1-A",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.UnitsInStock += args.UnitsToAdd;",
Values =
{
{"UnitsToAdd", 10}
}
},
patchIfMissing: null));
Add or increment
AddOrIncrement
increments an existing field or adds a new one in documents where they didn't exist.
// While running AddOrIncrement specify <entity type, field type>
session.Advanced.AddOrIncrement<User, int>(
// Specify document id and entity on which the operation should be performed.
id,
new User
{
FirstName = "John",
LastName = "Doe",
LoginCount = 1
// The path to the field and value to be added.
}, x => x.LoginCount, 1);
session.SaveChanges();
Add or patch
AddOrPatch
adds or edits field(s) in a single document.
If the document doesn't yet exist, this operation adds the document but doesn't patch it.
// While running AddOrPatch specify <entity type, field type>
session.Advanced.AddOrPatch<User, DateTime>(
// Specify document id and entity on which the operation should be performed.
id,
new User
{
FirstName = "John",
LastName = "Doe",
LastLogin = DateTime.Now
},
// The path to the field and value to set.
x => x.LastLogin, new DateTime(2021, 9, 12));
session.SaveChanges();
Add or patch to an existing array
This sample shows how to patch an existing array or add it to documents where it doesn't yet exist.
// While running AddOrPatch specify <entity type, field type>
session.Advanced.AddOrPatch<User, DateTime>(
// Specify document id and entity on which the operation should be performed.
id,
new User
{
FirstName = "John",
LastName = "Doe",
LoginTimes =
new List<DateTime>
{
DateTime.UtcNow
}
},
// The path to the field
x => x.LoginTimes,
// Modifies the array
u => u.Add(new DateTime(1993, 09, 12), new DateTime(2000, 01, 01)));
session.SaveChanges();
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(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.push(args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: null));
session.SaveChanges();
// add a new comment to Comments
store.Operations.Send(new PatchOperation(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.push(args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: 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(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.splice(1, 0, args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.splice(1, 0, args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: 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(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.splice(3, 1, args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: null));
session.SaveChanges();
// modify a comment at position 3 in Comments
store.Operations.Send(new PatchOperation(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = "this.Comments.splice(3, 1, args.Comment);",
Values =
{
{
"Comment", new BlogComment
{
Content = "Lore ipsum",
Title = "Some title"
}
}
}
},
patchIfMissing: null));
Remove items from array
// filter out all comments of a blogpost which contains the word "wrong" in their contents
session.Advanced.Patch<BlogPost, BlogComment>("blogposts/1",
x => x.Comments,
comments => comments.RemoveAll(y => y.Content.Contains("wrong")));
session.SaveChanges();
// filter out all comments of a blogpost which contains the word "wrong" in their contents
session.Advanced.Defer(new PatchCommandData(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.Comments = this.Comments.filter(comment=>
!comment.Content.includes(args.Text));",
Values =
{
{"Text", "wrong"}
}
},
patchIfMissing: null));
session.SaveChanges();
// filter out all comments of a blogpost which contains the word "wrong" in their contents
store.Operations.Send(new PatchOperation(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.Comments = this.Comments.filter(comment=>
!comment.Content.includes(args.Text));",
Values =
{
{"Text", "wrong"}
}
},
patchIfMissing: null));
Loading documents in a script
Loading documents is supported only by the non-typed APIs.
// update product names in order, according to loaded product documents
session.Advanced.Defer(new PatchCommandData(
id: "orders/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.Lines.forEach(line=> {
var productDoc = load(line.Product);
line.ProductName = productDoc.Name;
});"
}, patchIfMissing: null));
session.SaveChanges();
// update product names in order, according to loaded product documents
store.Operations.Send(new PatchOperation(
id: "blogposts/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"this.Lines.forEach(line=> {
var productDoc = load(line.Product);
line.ProductName = productDoc.Name;
});"
},
patchIfMissing: null));
Remove property
Removing property supported only by the non-typed APIs.
// remove property Age
session.Advanced.Defer(new PatchCommandData(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"delete this.Age;"
},
patchIfMissing: null));
session.SaveChanges();
// remove property Age
store.Operations.Send(new PatchOperation(
id: "employees/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"delete this.Age;"
},
patchIfMissing: null));
Rename property
Renaming property supported only by the non-typed APIs.
// rename FirstName to Name
session.Advanced.Defer(new PatchCommandData(
id: "employees/1",
changeVector: null,
patch: 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"
}
}
}
},
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "employees/1",
changeVector: null,
patch: 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"
}
}
}
},
patchIfMissing: null));
Add document
Adding a new document is supported only by the non-typed APIs.
session.Advanced.Defer(new PatchCommandData("employees/1-A", null,
new PatchRequest
{
Script = "put('orders/', { Employee: id(this) });",
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("employees/1-A", null, new PatchRequest
{
Script = "put('orders/', { Employee: id(this) });",
}));
Clone document
In order to clone a document use put method as follows.
session.Advanced.Defer(new PatchCommandData("employees/1-A", null,
new PatchRequest
{
Script = "put('employees/', this);",
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("employees/1-A", null, new PatchRequest
{
Script = "put('employees/', this);",
}));
Attachments, Counters, Time Series, and Revisions:
Attachments, counters, time series data, and revisions from the source document will Not be copied
to the new document automatically.
Increment counter
In order to increment or create a counter use incrementCounter
method as follows:
var order = session.Load<Order>("orders/1-A");
session.CountersFor(order).Increment("Likes", 1);
session.SaveChanges();
session.Advanced.Defer(new PatchCommandData("orders/1-A", null,
new PatchRequest
{
Script = "incrementCounter(this, args.name, args.val);",
Values =
{
{ "name", "Likes" },
{ "val", 20 }
}
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("orders/1-A", null, new PatchRequest
{
Script = "incrementCounter(this, args.name, args.val);",
Values =
{
{ "name", "Likes" },
{ "val", -1 }
}
}));
Method overloading & value restrictions
The method can be called by document ID or by document reference and the value can be negative.
Delete counter
In order to delete a counter use deleteCounter
method as follows:
session.CountersFor("orders/1-A").Delete("Likes");
session.SaveChanges();
session.Advanced.Defer(new PatchCommandData("products/1-A", null,
new PatchRequest
{
Script = "deleteCounter(this, args.name);",
Values =
{
{ "name", "Likes" },
}
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("products/1-A", null, new PatchRequest
{
Script = "deleteCounter(this, args.name);",
Values =
{
{ "name", "Likes" },
}
}));
Method overloading
The method can be called by document ID or by document reference
Get counter
In order to get a counter while patching use counter
method as follows:
var order = session.Load<Order>("orders/1-A");
var counters = session.Advanced.GetCountersFor(order);
session.Advanced.Defer(new PatchCommandData("orders/1-A", null,
new PatchRequest
{
Script = @"var likes = counter(this.Company, args.name);
put('result/', {company: this.Company, likes: likes});",
Values =
{
{ "name", "Likes" },
}
}, null));
session.SaveChanges();
store.Operations.Send(new PatchOperation("orders/1-A", null, new PatchRequest
{
Script = @"var likes = counter(this.Company, args.name);
put('result/', {company: this.Company, likes: likes});",
Values =
{
{ "name", "Likes" },
}
}));
Method overloading
The method can be called by document ID or by document reference.