Saving a new document

Before we can start saving information to RavenDB, we must define what we will save. For the purpose of these sections, we will use the following class structure:

    public class BlogPost
    {
    	public string Id { get; set; }
    	public string Title { get; set; }
    	public string Category { get; set; }
    	public string Content { get; set; }
    	public DateTime PublishedAt { get; set; }
    	public string[] Tags { get; set; }
    	public BlogComment[] Comments { get; set; }
    }
    
    public class BlogComment
    {
    	public string Title { get; set; }
    	public string Content { get; set; }
    }
    

To save a new blog post in our database server, we first create a new instance of the BlogPost class, as shown below:

    // Creating a new instance of the BlogPost class
    BlogPost post = new BlogPost()
    					{
    						Title = "Hello RavenDB",
    						Category = "RavenDB",
    						Content = "This is a blog about RavenDB",
    						Comments = new BlogComment[]
                    		           	{
                    		           		new BlogComment() {Title = "Unrealistic", Content = "This example is unrealistic"},
                    		           		new BlogComment() {Title = "Nice", Content = "This example is nice"}
    
                    		           	}
    					};
    

Neither the class itself nor instantiating it requires anything from RavenDB, either in the form of attributes or in the form of special factories. The RavenDB Client API works with simple POCO (Plain Old CLR Objects) objects.

Persisting this entire object graph involves calling Store and then SaveChanges, on a session object we obtained from our document store:

    // Saving the new instance to RavenDB
    session.Store(post);
    session.SaveChanges();
    
    

The Store method operates purely in memory and only the call of SaveChanges will produce the actual communication with the server (a single POST by using request batching).

Note that the entire object graph will serialized and persisted as a single document on the server side, not as a set of distinct objects.

Document IDs

In the example above we had a string Id property for BlogPost, and left it blank. It is this property that will be used as the "primary key" for this document. Note how RavenDB generated an ID for us, "BlogPosts/1", based on the default convention which we will discuss in a second.

If there is no Id property on a document, RavenDB will still generate a unique ID, but it will be retrievable only by calling session.Advanced.GetDocumentId(object). In other words, having an Id property is entirely optional, so you can explicitly define such a property only when you need this information to be more accessible.

Document ID limitations

  1. Maximum number of characters is 1023
  2. Id cannot contain \

Document IDs generation strategies

RavenDB supports 3 ways of figuring out a unique id, or a document key, for a newly saved document.

By default, the HiLo algorithm is used. Whenever you create a new DocumentStore object and it connects to a RavenDB server, HiLo keys are exchanged and your connection is assigned a range of values it can use when a new entity is being stored. A new set of values is negotiated automatically if the range was consumed.

When you store an object with no Id property, or with one that wasn't manually set, RavenDB will assign it with a document ID that is combined of the collection name (which by default is the entity name in its plural form) and the next available ID from the given range.

It is important to understand that a Collection is merely a convention, not something that is enforced by RavenDB. There is absolutely nothing that would prevent you from saving a Post with the document id of "users/1", and that would overwrite any existing document with the id "users/1", regardless of which collection it belongs to.

Numeric or Guid Id properties are supported and will work seamlessly. In this case, RavenDB will automatically make the translation between the inner string ID to the numeric or Guid value shown in the entity and back.

Using this approach, IDs are available immediately after calling Store on the object - the RavenDB session will set a unique ID in memory without asking one from the server.

RavenDB also supports the notion of Identity, for example if you need IDs to be consecutive. By creating a string Id property in your entity, and setting it to a value ending with a slash (/), you can tell RavenDB to use that as a key prefix for your entity. That prefix followed by the next available integer ID for it will be your entity's ID after you call SaveChanges().

You can also assign an ID manually by explicitly setting the string Id property of your object, but then if a document already exists in your RavenDB server under the same key it will be overwritten without any warning.

If you are going to store large number of documents with custom generated IDs, you must necessarily read the topic Dealing with custom ID for high number of documents presented below.

Custom ID generation strategies

You can also setup a custom id generation strategy by supplying a DocumentKeyGenerator to the document store conventions, like so:

    store.Conventions.DocumentKeyGenerator = (dbname, commands, entity) => store.Conventions.GetTypeTagName(entity.GetType()) + "/";
    
    

This will instruct RavenDB to use identity id generation strategy for all the entities that this document store manages.

Overriding default ID generation

To override default document key generation algorithms, we added RegisterIdConvention and RegisterAsyncIdConvention methods to DocumentConvention where you can include your own identifier generation logic.

    DocumentConvention RegisterIdConvention<TEntity>(Func<string, IDatabaseCommands, TEntity, string> func);
    
    DocumentConvention RegisterAsyncIdConvention<TEntity>(Func<string, IAsyncDatabaseCommands, TEntity, Task<string>> func);
    
    

Consider a User class:

    public class User
    {
    	public string Name { get; set; }
    
    	public string FirstName { get; set; }
    
    	public string LastName { get; set; }
    }
    
    

To generate a custom Id with users/ prefix combined with Name of the user you need to do as follows:

    store.Conventions.RegisterIdConvention<User>((dbname, commands, user) => "users/" + user.Name);
    
    

or if you want to register your convention for async operations then:

    store.Conventions.RegisterAsyncIdConvention<User>((dbname, commands, user) => new CompletedTask<string>("users/" + user.Name));
    
    

Note that spectrum of identifier generation abilities is very wide, because DatabaseCommands object is passed into an identifier convention function and can be used for advanced calculation techniques.

As of version 2.0, the dbname is passed to the register convention methods to allow users to make Id generation decision per database (e.g. default document key generator - the MultiDatabaseHiloGenerator - is using this parameter to prevent sharing HiLo values accross the databases)

    using (var session = store.OpenSession())
    {
    	session.Store(new User
    	{
    		Name = "jdoe",
    		FirstName = "John",
    		LastName = "Doe"
    	});
    
    	session.SaveChanges();
    }
    
    

Above code will store new entity in the database with users/jdoe as a key and below code will store user using async operation with users/jcarter key:

    using (var session = store.OpenAsyncSession())
    {
    	await session.StoreAsync(new User
    	{
    		Name = "jcarter",
    		FirstName = "John",
    		LastName = "Carter"
    	});
    
    	await session.SaveChangesAsync();
    }
    
    

Inheritance

Registered conventions are inheritance-aware, so all types that can be assigned from registered type will fall into that convention according to inheritance-hierarchy tree

If we will create a new class PrivilegedUser that will derive from our User class

    using (var session = store.OpenAsyncSession())
    {
    	await session.StoreAsync(new User
    	{
    		Name = "jcarter",
    		FirstName = "John",
    		LastName = "Carter"
    	});
    
    	await session.SaveChangesAsync();
    }
    
    

then if we will add convention for User, both our types will use our custom convention

    store.Conventions.RegisterIdConvention<User>((dbname, commands, user) => "users/" + user.Name);
    
    using (var session = store.OpenSession())
    {
    	session.Store(new User // users/jdoe
    	{
    		Name = "jdoe",
    		FirstName = "John",
    		LastName = "Doe"
    	});
    
    	session.Store(new PrivilegedUser // users/jcarter
    	{
    		Name = "jcarter",
    		FirstName = "John",
    		LastName = "Carter"
    	});
    
    	session.SaveChanges();
    }
    
    

If we register two conventions, one for User and second for PrivilegedUser then they will be picked for their specific types.

    store.Conventions.RegisterIdConvention<User>((dbname, commands, user) => "users/" + user.Name);
    store.Conventions.RegisterIdConvention<PrivilegedUser>((dbname, commands, user) => "admins/" + user.Name);
    
    using (var session = store.OpenSession())
    {
    	session.Store(new User // users/jdoe
    	{
    		Name = "jdoe",
    		FirstName = "John",
    		LastName = "Doe"
    	});
    
    	session.Store(new PrivilegedUser // admins/jcarter
    	{
    		Name = "jcarter",
    		FirstName = "John",
    		LastName = "Carter"
    	});
    
    	session.SaveChanges();
    }
    
    

Dealing with custom ID for high number of documents

The ways shown in this section give to you a great flexibility in creating identifiers of documents. You are able to assign to a document any ID as you can imagine. Everything is going to work correctly however you have to be aware that some kind of IDs might cause performance issues when number of documents with custom generated IDs is very high (millions of documents).

The very unrecommended way is to use non-sequential data as document IDs (e.g. created by hash functions). In such case you might be experiencing the declining performance of searching for an existing document and inserting new ones. If your intention is to use custom identifiers and you expect that very high number of such documents might be saved in your database, then we suggest to make use of incremented IDs in order to ensure the good performance.