see on GitHub

Operations : How to perform set based operations on documents

Sometimes we need to update a large amount of documents answering certain criteria. With SQL this is a simple operation, and a query doing that will look like this:

UPDATE Users SET IsActive = 0 WHERE LastLogin < '2010-01-01'

This is usually not the case for NoSQL databases, where set based operations are not supported. RavenDB does support them, and by passing it a query and an operation definition, it will run the query and perform that operation on its results.

The same queries and indexes that are used for data retrieval are used for the set based operations, therefore the syntax defining which documents to work on is exactly the same as you'd specified for those documents to be pulled from store.

Syntax

Sending patch request

Operation Send(PatchByQueryOperation operation);

Parameters
operation PatchByQueryOperation PatchByQueryOperation object, describing the query and the patch that will be performed
Return Value
Operation Object that allows waiting for operation to complete, it also may return information about performed patch, see examples below.

PatchByQueryOperation

Simple

This is a simpler overload of the PatchByQueryOperation ctor, it allows defining the RQL update statement, while all other parameters get's their default values. Best for working with non-stale data.

public PatchByQueryOperation(string queryToUpdate)

Parameters
queryToUpdate string RQL query defining the update operation. The RQL query starts as any other RQL query with "from" and "update" statements, but later, it continues with an "update" clause, in which you describe the javascript patch code

Full

public PatchByQueryOperation(IndexQuery queryToUpdate, QueryOperationOptions options = null)

Parameters
queryToUpdate IndexQuery RQL query defining the update operation. The RQL query starts as any other RQL query with "from" and "update" statements, but later, it continues with an "update" clause, in which you describe the javascript patch code
options QueryOperationOptions Options defining how the operation will be performed and various constraints on how it is performed

Remarks

Safe By Default

By default, Set based operations will not work on indexes that are stale, and the operation will only succeed if the specified index is not stale. This is to make sure you only delete what you intended to delete.

For indexes that are updated all the time, you can set the AllowStale field of QueryOperationOptions to true if you want to patch on stale results anyway.

Examples

Update whole collection

// increase by 10 Freight field in all orders
var operation1 = store.Operations.Send(
   new PatchByQueryOperation(@"from Orders as o
                               update
                               {
                                   o.Freight +=10;
                               }"));
// Wait for the operation to be complete on the server side.
// Not waiting for completion will not harm the patch process and it will continue running to completion.
operation1.WaitForCompletion();

Update by dynamic query

// set discount to all orders that was processed by a specific employee
var operation2 = store.Operations.Send(
    new PatchByQueryOperation(@"from Orders as o
                                where o.Employee = args.EmployeeToUpdate
                                update
                                {
                                    o.Lines.forEach(line=> line.Discount = 0.3);
                                }"));
operation2.WaitForCompletion();

Update by static index query result

// switch all products with supplier 'suppliers/12-A' with 'suppliers/13-A'
var operation3 = store.Operations.Send(
    new PatchByQueryOperation(new IndexQuery
    {
        Query = @"from index 'Product/Search' as p
                  where p.Supplier = 'suppliers/12-A'
                  update
                  {
                      p.Supplier = 'suppliers/13-A'
                  }"                        
    }));
operation3.WaitForCompletion();

Patch on stale results

// patch on stale results
var operation4 = store.Operations.Send(
    new PatchByQueryOperation(new IndexQuery
    {
        Query = @"from Orders as o
                  where o.Company = 'companies/12-A'
                  update
                  {
                      o.Company = 'companies/13-A'
                  }"
    },
    new QueryOperationOptions
    {
        AllowStale=true                        
    }
    ));
operation4.WaitForCompletion();

Report progress on patch

// report progress during patch processing
var operation5 = store.Operations.Send(
    new PatchByQueryOperation(new IndexQuery
    {
        Query = @"from Orders as o
                  where o.Company = 'companies/12-A'
                  update
                  {
                      o.Company = 'companies/13-A'
                  }"
    },
    new QueryOperationOptions
    {
        AllowStale = true
    }
    ));
operation5.OnProgressChanged = x =>
{
    DeterminateProgress progress = (DeterminateProgress)x;
    Console.WriteLine($"Progress: Processed:{progress.Total}; Total:{progress.Processed}");                    
};
operation5.WaitForCompletion();

Process patch results details

// perform patch and create summary of processing statuses
var operation6 = 
    store.Operations.Send(
        new PatchByQueryOperation(new IndexQuery
        {
            Query = @"from Orders as o
                      where o.Company = 'companies/12-A'
                      update
                      {
                          o.Company = 'companies/13-A'
                      }"
        },
        new QueryOperationOptions
        {                            
            RetrieveDetails =true
        }));

var result = operation6.WaitForCompletion<BulkOperationResult>();
var formattedResults =
    result.Details
    .Select(x => (BulkOperationResult.PatchDetails)x)
    .GroupBy(x => x.Status)
    .Select(x => $"{x.Key}: {x.Count()}").ToList();
formattedResults.ForEach(Console.WriteLine);

operation6.WaitForCompletion();