In last period, I am spending so times to learn document databases, in my case RavenDB and MongoDB. To be honest I am working only on Raven right now because it is friendlier for .NET developers but I promised myself to compare some features from these two awesome database engines.
One of the big difficult I found, is to create the right model. I said big because I’m used to design the model for relation database and here is really different. For example we do not have the join and we also need to denormalize the references (http://ravendb.net/docs/faq/denormalized-references).
It is mandatory because each document must be independent as Oren wrote on the site:
One of the design principals that RavenDB adheres to is the idea that documents are independent, that all the data required to process a document is stored within the document itselfFortunately, Raven makes easy some stuff like update and loading, respectively using Patch API (http://ravendb.net/docs/client-api/partial-document-updates) and Include.
The Include command is absolutely cool, it makes easy to load two documents with one roundtrip and it means only one thing, Faster! For the NHibernate users it reminds me a little the Future command, with an important different, here you can load only document that has a key to another one, in NH you can load also objects completely unlinked.
Let’s see my domain:
It seems more complex that it really is. There is a Post class and ItemComments. The first one includes all you need to have in the aggregate view; the second one, combined with the first one, has all you need to show in the detail view or admin area (in the most blog enginea you don’t need to show the comments in the main page, otherwise you need in the permalink).
The only common thing between them is the property CommentsId into Post class (It’s called Id in ItemComments). With it, you can easily load both documents in the same time. Let’s see how to use it:
Post item = new Post()
//populate the properties with your values
this.Session.Store(item);
ItemComments comments = new ItemComments
{
Approved = new List<Comment>(),
Pending = new List<Comment>(),
Spam = new List<Comment>(),
Item = new ItemReference
{
Id = post.Id,
Status = post.Status,
ItemPublishedAt = post.PublishAt
}
};
this.Session.Store(comments);
post.CommentsId = comments.Id;
this.Session.SaveChanges();
//To read both with only one request:
Post post = this.Session
.Include<Post>(x => x.CommentsId)
.Load(id);
ItemComments comments = this.Session.Load<ItemComments>(post.CommentsId);
As you see in the diagram, the key for the Post is a simple integer but in the ItemComments it is a string. That is mandatory if you want to use the Include because in the string there is all Raven needs to load the document (the ID and the type, in this case ItemComments/12345 where 12345 is the id).
Different CLR types for the key means to extend the base class for all entities. Typically I have a base class (EntityBase in this model) that includes the ID, Creation Date and a calculate property to check the status of the entity.
public class EntityBase<T>
{
#region Public Properties
public DateTimeOffset CreatedAt { get; set; }
public T Id { get; set; }
public bool IsTransient
{
get
{
return this.Id == 0;
}
}
#endregion
}
The property IsTransient could be helpful to understand if you have to execute some path or not. Using a generic CLR type for the ID the base class must be extended like this:
public class EntityBase<T>
{
#region Public Properties
public DateTimeOffset CreatedAt { get; set; }
public T Id { get; set; }
public bool IsTransient
{
get
{
return EqualityComparer<T>.Default.Equals(Id, default(T));
}
}
#endregion
}
Now you can still use a base class with the same features having fun with RavenDB.