What is a Session and How Does it Work



Unit of work pattern

Tracking changes

  • Using the session, perform needed operations on your documents.
    e.g. create a new document, modify an existing document, query for documents, etc.
  • Any such operation 'loads' the document as an entity to the Session,
    and it is added to the Session's entities map.
  • The session tracks all changes made to all entities stored in its internal map.
    You don't need to manually track the changes to these entities and decide what needs to be saved and what doesn't.
    The session will do it for you.
  • All these tracked changes are combined & persisted in the database only when calling saveChanges().
  • Entity tracking can be disabled if needed. See:

Create document example

  • The Client API, and the session in particular, is designed to be as straightforward as possible.
    Open the session, do some operations, and apply the changes to the RavenDB server.
  • The following example shows how to create a new document in the database using the session.

// Obtain a session from your Document Store
const session = documentStore.openSession();

// Create a new company entity
class Company {
    constructor(name) {
        this.name = name;
    }
}

const entity = new Company("CompanyName");

// Store the entity in the Session's internal map
await session.store(entity);
// From now on, any changes that will be made to the entity will be tracked by the session.
// However, the changes will be persisted to the server only when 'saveChanges()' is called.

await session.saveChanges();
// At this point the entity is persisted to the database as a new document.
// Since no database was specified when opening the session, the Default Database is used.
// Obtain a session from your Document Store
const session = documentStore.openSession();

// Create a new company entity
class Company {
    constructor(name) {
        this.name = name;
    }
}

const entity = new Company("CompanyName");

// Store the entity in the Session's internal map
session.store(entity)
    .then(() => {
        // From now on, any changes that will be made to the entity will be tracked by the session.
        // However, the changes will be persisted to the server only when 'saveChanges()' is called.
        
        session.saveChanges();
    })
    .then(() => {
        // At this point the entity is persisted to the database as a new document.
        // Since no database was specified when opening the session, the Default Database is used.
    });

Modify document example

  • The following example modifies the content of an existing document.

// Open a session
const session = documentStore.openSession();

// Load an existing document to the session using its ID
// The loaded entity will be added to the session's internal map
const entity = await session.load("companies/1-A");

// Edit the entity, the session will track this change
entity.name = "NewCompanyName";

await session.saveChanges();
// At this point, the change made is persisted to the existing document in the database
// Open a session
const session = documentStore.openSession();

// Load an existing document to the session using its ID
// The loaded entity will be added to the session's internal map
session.load("companies/1-A")
    .then((company) => {
        // Edit the entity, the session will track this change
        company.name = "NewCompanyName";
    })
    .then(() => session.saveChanges())
    .then(() => {
        // At this point, the change made is persisted to the existing document in the database
    });

Identity map pattern

  • The session implements the Identity Map Pattern.
  • The first load() call goes to the server and fetches the document from the database.
    The document is then stored as an entity in the session's entities map.
  • All subsequent load() calls to the same document will simply retrieve the entity from the session -
    no additional calls to the server are made.

// A document is fetched from the server
const entity1 = await session.load("companies/1-A");

// Loading the same document will now retrieve its entity from the session's map
const entity2 = await session.load("companies/1-A");

// This command will Not throw an exception.
assert.equal(entity1, entity2);
  • Note:
    To override this behavior and force load() to fetch the latest changes from the server see: Refresh an entity.

Batching & Transactions

Batching

  • Remote calls to a server over the network are among the most expensive operations an application makes.
    The session optimizes this by batching all write operations it has tracked into the saveChanges() call.
  • When calling saveChanges, the session checks its state for all changes made that need to be saved in the database,
    and combines them into a single batch that is sent to the server as a single remote call.

Transactions

  • The batched operations that are sent in the saveChanges() will complete transactionally.
    In other words, either all changes are saved as a Single Atomic Transaction or none of them are.
    So once saveChanges returns successfully, it is guaranteed that all changes are persisted to the database.
  • The saveChanges is the only time when a RavenDB client sends updates to the server from the session,
    so you will experience a reduced number of network calls.

Transaction mode

  • The session's transaction mode can be set to either:
    • Single-Node - transaction is executed on a specific node and then replicated
    • Cluster-Wide - transaction is registered for execution on all nodes in an atomic fashion
  • Learn more about these modes in Cluster-wide vs. Single-node transactions.

Reducing server calls (best practices) for:

The select N+1 problem

  • The Select N+1 problem is common with all ORMs and ORM-like APIs.
    It results in an excessive number of remote calls to the server, which makes a query very expensive.
  • Make use of RavenDB's include() method to include related documents and avoid this issue.
    See: Document relationships

Large query results

  • When query results are large and you don't want the overhead of keeping all results in memory,
    then you can Stream query results.
    A single server call is executed and the client can handle the results one by one.
  • Paging also avoids getting all query results at one time,
    however, multiple server calls are generated - one per page retrieved.

Retrieving results on demand (Lazy)

  • Query calls to the server can be delayed and executed on-demand as needed using Lazily()
  • See Perform queries lazily