Session: Saving changes

Pending session operations e.g. Store, Delete and many others will not be send to the server until SaveChanges is called.

Whenever you execute SaveChanges() to send a batch of operations like put, update, or delete in a request,
the server will wrap these operations in a transaction upon execution in the database.

Either all operations will be saved as a single, atomic transaction or none of them will be.
Once SaveChanges() returns successfully, it is guaranteed that all changes are persisted in the database.

Syntax

void SaveChanges();
Task SaveChangesAsync();

Example

// storing new entity
session.Store(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

session.SaveChanges();
// storing new entity
await asyncSession.StoreAsync(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

await asyncSession.SaveChangesAsync();

Waiting for Indexes

You can ask the server to wait until the indexes are caught up with changes made within the current session before the SaveChanges returns.

  • You can set a timeout (default: 15 seconds).
  • You can specify whether you want to throw on timeout (default: false).
  • You can specify indexes that you want to wait for. If you don't specify anything here, RavenDB will automatically select just the indexes that are impacted by this write.

session.Advanced.WaitForIndexesAfterSaveChanges(
    timeout: TimeSpan.FromSeconds(30),
    throwOnTimeout: true,
    indexes: new[] { "index/1", "index/2" });

session.Store(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

session.SaveChanges();
asyncSession.Advanced.WaitForIndexesAfterSaveChanges(
    timeout: TimeSpan.FromSeconds(30),
    throwOnTimeout: true,
    indexes: new[] { "index/1", "index/2" });

await asyncSession.StoreAsync(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

await asyncSession.SaveChangesAsync();

Waiting for Replication - Write Assurance

Sometimes you might need to ensure that changes made in the session will be replicated to more than one node of the cluster before the SaveChanges returns. It can be useful if you have some writes that are really important so you want to be sure the stored values will reside on multiple machines. Also it might be necessary to use when you customize the read balance behavior and need to ensure the next request from the user will be able to read what he or she just wrote (the next open session might access a different node).

You can ask the server to wait until the replication is caught up with those particular changes.

  • You can set a timeout (default: 15 seconds).
  • You can specify whether you want to throw on timeout, which may happen in case of network issues (default: true).
  • You can specify to how many replicas (nodes) the currently saved write must be replicated, before the SaveChanges returns (default: 1).
  • You can specify whether the SaveChanges will return only when the current write was replicated to majority of the nodes (default: false).

session.Advanced.WaitForReplicationAfterSaveChanges(
    timeout: TimeSpan.FromSeconds(30),
    throwOnTimeout: false, //default true
    replicas: 2, //minimum replicas to replicate
    majority: false);

session.Store(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

session.SaveChanges();
asyncSession.Advanced.WaitForReplicationAfterSaveChanges(
    timeout: TimeSpan.FromSeconds(30),
    throwOnTimeout: false, //default true
    replicas: 2, //minimum replicas to replicate
    majority: false);

await asyncSession.StoreAsync(new Employee
{
    FirstName = "John",
    LastName = "Doe"
});

await asyncSession.SaveChangesAsync();

Important

The WaitForReplicationAfterSaveChanges waits only for replicas which are part of the cluster. It means that external replication destinations are not counted towards the number specified in replicas parameter, since they are not part of the cluster.

Important

Even if RavenDB was not able to write your changes to the number of replicas you specified, the data has been already written to some nodes. You will get an error but data is already there.

This is a powerful feature, but you need to be aware of the possible pitfalls of using it.

Transaction Mode - Cluster Wide

Setting TransactionMode to TransactionMode.ClusterWide will enable the Cluster Transactions feature.

With this feature enabled the Session will support the following write commands:

  • Store/StoreAsync
  • Delete
  • CreateCompareExchangeValue
  • UpdateCompareExchangeValue
  • DeleteCompareExchangeValue

Here is an example of creating a unique user with cluster wide.

using (var store = new DocumentStore())
{
    using (var session = store.OpenSession(new SessionOptions
    {
        //default is:     TransactionMode.SingleNode
        TransactionMode = TransactionMode.ClusterWide
    }))
    {
        var user = new Employee
        {
            FirstName = "John",
            LastName = "Doe"
        };
        session.Store(user);

        // this transaction is now conditional on this being 
        // successfully created (so, no other users with this name)
        // it also creates an association to the new user's id
        session.Advanced.ClusterTransaction
            .CreateCompareExchangeValue("usernames/John", user.Id);

        session.SaveChanges();
    }
using (var session = store.OpenAsyncSession(new SessionOptions
{
    //default is:     TransactionMode.SingleNode
    TransactionMode = TransactionMode.ClusterWide
}))
{
    var user = new Employee
    {
        FirstName = "John",
        LastName = "Doe"
    };
    await session.StoreAsync(user);

    // this transaction is now conditional on this being 
    // successfully created (so, no other users with this name)
    // it also creates an association to the new user's id
    session.Advanced.ClusterTransaction
        .CreateCompareExchangeValue("usernames/John", user.Id);

    await session.SaveChangesAsync();
}