Skip to main content

What is a Session and How Does it Work

Session overview

  • What is the session:

    • The session (ISession/IAsyncDocumentSession) serves as a Unit of Work representing a single
      Business Transaction on a specific database (not to be confused with an ACID transaction).

    • It is a container that allows you to query for documents and load, create, or update entities
      while keeping track of changes.

    • Basic document CRUD actions and document Queries are available through the Session.
      More advanced options are available using the Advanced Session operations.

  • Batching modifications:
    A business transaction usually involves multiple requests such as loading of documents or execution of queries.
    Calling SaveChanges() indicates the completion of the client-side business logic. At this point, all modifications made within the session are batched and sent together in a single HTTP request to the server to be persisted as a single ACID transaction.

  • Tracking changes:
    Based on the Unit of Work and the Identity Map patterns, the session tracks all changes made to all entities that it has either loaded, stored, deleted, or queried for.
    Only the modifications are sent to the server when SaveChanges() is called.

  • Client side object:
    The session is a pure client side object. Opening the session does Not establish any connection to a database,
    and the session's state isn't reflected on the server side during its duration.

  • Configurability:
    Various aspects of the session are configurable.
    For example, the number of server requests allowed per session is configurable (default is 30).

  • The session and ORM Comparison:
    The RavenDB Client API is a native way to interact with a RavenDB database.
    It is not an Object–relational mapping (ORM) tool. Although if you're familiar with NHibernate of Entity Framework ORMs you'll recognize that the session is equivalent of NHibernate's session and Entity Framework's DataContext which implement UoW pattern as well.

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 the entity 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 and decide what needs to be saved and what doesn't,
    the Session will do it for you.
  • Prior to saving, you can review the changes made if necessary. See:
  • All the 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
using (IDocumentSession session = store.OpenSession())
{
// Create a new entity
Company entity = new Company { Name = "CompanyName" };

// Store the entity in the Session's internal map
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.

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.
}

Modify document example

  • The following example modifies the content of an existing document.
// Open a session
using (IDocumentSession session = store.OpenSession())
{
// Load an existing document to the Session using its ID
// The loaded entity will be added to the session's internal map
Company entity = session.Load<Company>(companyId);

// Edit the entity, the Session will track this change
entity.Name = "NewCompanyName";

session.SaveChanges();
// 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
Company entity1 = session.Load<Company>(companyId);

// Loading the same document will now retrieve its entity from the Session's map
Company entity2 = session.Load<Company>(companyId);

// This command will Not throw an exception
Assert.Same(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 evaluates its state to identify all pending changes requiring persistence in the database. These changes are then combined into a single batch that is sent to the server in a single remote call and executed as a single ACID transaction.

Transactions

  • The client API does not provide transactional semantics over the entire session.
    The session does not represent a transaction (nor a transaction scope) in terms of ACID transactions.
  • RavenDB provides transactions over individual requests, so each call made within the session's usage will be processed in a separate transaction on the server side. This applies to both reads and writes.
Read transactions
  • Each call retrieving data from the database will generate a separate request. Multiple requests mean separate transactions.
  • The following options allow you to read multiple documents in a single request:
    • Using overloads of the Load() method that specify a collection of IDs or a prefix of ID.
    • Using Include to retrieve additional documents in a single request.
    • A query that can return multiple documents is executed in a single request,
      hence it is processed in a single read transaction.
Write transactions
  • The batched operations that are sent in the SaveChanges() complete transactionally, as this call generates a single request to the database. 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.
  • SaveChanges is the only time when the RavenDB Client API sends updates to the server from the Session,
    resulting in a reduced number of network calls.
  • To execute an operation that both loads and updates a document within the same write transaction, use the patching feature. This can be done either with the usage of a JavaScript patch syntax or JSON Patch syntax.

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
    • The phrase "session's transaction mode" refers to the type of transaction that will be executed on the server-side when SaveChanges() is called. As mentioned earlier, the session itself does not represent an ACID transaction.

    • Learn more about these modes in Cluster-wide vs. Single-node transactions.

Transactions in RavenDB

For a detailed description of transactions in RavenDB please refer to the Transaction support in RavenDB article.

Concurrency control

A session may load documents, modify them in memory, and save the changes back to the database.
Since loading/querying and saving occur in separate requests, other sessions or clients may modify the same documents during this time - between when your session loads the document and when SaveChanges() is called.

By default, sessions do not detect these conflicts and use a "Last Write Wins" strategy, where the most recent SaveChanges() call overwrites any prior changes made by other sessions.

If your application requires detecting and preventing conflicting updates rather than silently overwriting them, you can enable optimistic concurrency, which ensures documents have not been modified by other sessions since they were loaded into your session.

See Enable optimistic concurrency for detailed behavior and configuration options.

Reducing server calls (best practices)

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

In this article