Raven’s Client API

The Raven Client API allows easy access from any .NET language to the Raven server or embedded database. Using the Raven Client API, you can access all aspects of the Raven server.

In addition to managing the pure communication side between the client and server, the Raven Client API is also responsible for a complete integrated experience for the .NET client. Among other things, the Client API is responsible for implementing the Unit of Work pattern, applying conventions to the saving/loading of data, integrating with System.Transactions, detecting changed entities, batching requests to the server and more.

Client API design guidelines

The Raven Client API design intentionally mimics the widely successful NHibernate API. The API is composed of the following main classes:

  • IDocumentStore - This is expensive to create, thread safe and should only be created once per application. The Document Store is used to create DocumentSessions, to hold the conventions related to saving/loading data and any other global configuration.
  • IDocumentSession - Instances of this interface are created by the DocumentStore, they are cheap to create and not thread safe. If an exception is thrown by an IDocumentSession method, the behavior of all of the methods (except Dispose) is undefined. The document session is used to interact with the Raven database, load data from the database, query the database, save and delete. Instances of this interface implement the Unit of Work pattern and change tracking. 
  • IDocumentQuery - Allows querying the indexes on the Raven server.

Getting started   - Server vs. Embedded

In general, Raven supports two running modes: running in a client/server mode, via HTTP (this is the recommended mode) and an embedded mode, in which the client API makes direct calls against the Database API.

In either mode, when the application shuts down, the document store instance(s) used should be disposed to ensure proper cleanup.

Running in embedded mode (Raven.Client.Embedded.dll)

Running in embedded mode is done by passing the path to the directory that the database resides in to the EmbeddableDocumentStore (the database will be created if it doesn't exists):

var documentStore = new EmbeddableDocumentStore  {  DataDirectory = "path/to/database/directory"  };
documentStore.Initialize();

 

Running in server mode (Raven.Client.Lightweight.dll)

var documentStore = new DocumentStore { Url = "http://ravendb.net/" };
documentStore.Initialize();

Using the session

Once we have a document store instance, we can start working with Raven for real. We start by opening a session:

string companyId;
using(var session = documentStore.OpenSession())
{
       var entity = new Company { Name = "Company" };
       session.Store(entity);
       session.SaveChanges();
       companyId = entity.Id;
}


using(var session = documentStore.OpenSession())
{
       var entity =  session.Load<Company>(companyId);
       Console.WriteLine(entity.Name);
}

 

As you can see, the API is straightforward. Open the session, do some operations, and finally save the changes to the Raven database. The second session is similar: open the session, get a document from Raven and do something with it.

Unit of Work

The Client API implements the Unit of Work pattern. That has several implications:

  • In the context of a single session, a single document (identified by its key) always resolves to the same instance.
    Assert.Same(session.Load<Company>(companyId),  session.Load<Company>(companyId))
  • The session manages change tracking for all of the entities that it has either loaded or stored.
using(var session = documentStore.OpenSession())
{
       var entity =  session.Load<Company>(companyId);
       entity.Name = "Another Company"; 
       session.SaveChanges(); // will send the change to the database
}

Entities and Associations

The Raven Client API considers a document to be the equivalent of an object graph with a particular root. In other words, the following object graph when stored:

new Company  
{  
     Name = "Hibernating Rhinos",  
     Employees =  
     {
        new Employee
       {  
           Name = "Ayende Rahien"  
       }  
    }  
}

Will result in the following single document being saved:

{
    "Name": "Hibernating Rhinos", 
    "Employees": [
        { "Name": "Ayende Rahien" } 
     ]
}

Developers coming from a relational world may find it surprising, since they would expect two documents to result from the object graph above, one representing the Company and a second representing the Employee.

By design, the Raven Client API doesn't try to split an object graph into component parts, but treats the entire object graph as a single document. For more on the design principles leading to this, you can read here.

What it means, in effect, is that if you want to store an association to another document in your persisted entities, you can't store a direct reference to another root entity. If you do, the Raven Client API will simply embed that reference in its  entirety. Instead of holding a direct reference to another root entity, you need to hold a key to that entity. 

This isn't a technical limitation. Implementing this sort of support is fairly straightforward from a technical perspective. The reason it isn't there is that it isn't natural for a document database and it leads to much more complex scenarios down the road with regards to scaling and sharding. For more along that line of reasoning, see here.

Batching

One of the most expensive things that you can do in your application is make remote calls. The Raven Client API optimizes this for you by batching all write calls to the Raven server into a single call. This is the default behavior, you don't have to do anything to enable it. This also ensures that writes to the database are always executed in a single transaction, no matter how many operations you are actually executing.

Concurrency

Raven supports optimistic concurrency to avoid overwriting other people's changes. By default, this behavior is turned off. You can turn it on by setting the IDocumentSession.UseOptimisticConcurrency property to true. When that is enabled, attempting to write to a document that was modified by another user will raise a ConcurrencyException.

This is done using Raven's support for etags, discussed here.

System Transactions Integration

The Raven Client API also supports System Transactions. In order to use that, all you need to do is run the Raven Client API under a Transaction Scope:

using(var tx = new TransactionScope())
using(var session = documentStore.OpenSession())
{
       var entity = new Company { Name = "Company" };
       session.Store(entity);
       session.SaveChanges();
       tx.Complete();
}

Querying

The Raven Client API exposes the full power of Raven querying. You can read more on querying and indexing in Raven. There are 2 ways of querying Raven:

  • Using the built-in LINQ provider that implements all the parts of standard IQueryable interface that map to Lucene. This will automatically translate the LINQ query into the relevant Lucene query and handle the low-level details for you. The query will look familiar to anyone who has used LINQ before:
var companies = session.Query<Company>()
       .Where(x => x.Region == "Asia")
       .Take(5);
  • Performing a low-level Lucene query directly. This has the advantage that you can access all the functionality of the Lucene querying API, but you have to build the queries yourself using strings that Lucene understands. A Lucene query looks like this:

var companies = session.Advanced.LuceneQuery<Company>()
       .Where("Region:Asia")
       .OrderBy("Phone")
       .ToArray();

Because Raven follows the "better stale than offline" approach, querying an index may return stale results. You can check for that in the query object, which will indicate if the results are stale or not. You can also wait for non stale results by calling  WaitForNonStaleResultsAsOfNow() on the query when using LuceneQuery() and using the Customize() method WaitForNonStaleResultsAsOfNow() using the Linq API to call.


Conventions

The Raven Client API uses several conventions to control how it works, these can be modified at the DocumentStore level.

  • FindIdentityProperty - Tell the Raven Client API how to find the property serving as the id property (the one holding the document key).
  • FindTypeTagName - Find the tag name for the entity, a tag name is the collection name in which an entity will be enrolled, as well as the default entity key namespace.
  • GenerateDocumentKey - Allows you to control the generation of keys for new entities. The rules for returned values follow the Raven document key generation strategies. By default, Raven uses the HiLo algorithm to generate new keys.
  • IdentityPartsSeparator - A string that allow you to customize part of the document key generation. By default, Raven uses "plural_entity_name/hilo_number", which some users don't like because it makes putting the document key in the URL harder in some cases. You can set this to another value (such as "-"), which would generate: "plural_entity_name-hilo_number". This is an alternative to replacing the whole document key generation process.