;

Adding a RavenDB persistence layer to an existing application

Posted : Monday, 19 March 2012 19:48:12

I recently read an article about RavenDB and it sounds awesome! If you don’t know too much about NoSQL or document based databases then have a bit of a Google of it or maybe just head straight to the RavenDB project page. It’s got a fairly comprehensive documentation with examples and updates being added all the time. I wanted to see how easy it was to add RavenDB based persistence to an existing application and this blog post is a summary of the experience.

A while ago I did a series of posts around persistence ignorance using an online auction application. For this post I used the same application and added a RavenDB based persistence layer to it. The first thing to do is install RavenDB – according to the documentation the recommended way is to use Nuget however I didn’t use Nuget for the other persistence technologies so for this post I downloaded the binaries, installed them manually then copied the required client assemblies into my solution. I added the relevant references and stub classes (for details of the required classes and application structure please consult the link at the start of this paragraph) to my new class library project to create the project structure shown below:

project structure_thumb[1]

The compiler raised a design-time error stating I had to add a reference to System.Component.Model, I am guessing that this is required by RavenDB so added it and the project built successfully. Next I implemented RavenDBModule which is responsible for wiring up the persistence layer. RavenDB has been designed to operate in a similar fashion as NHibernate in terms of sessions – in the case of RavenDB the recommendation is to create a single instance of IDocumentStore per application and use that to create Sessions as necessary. In the RavenDBModule code I wire up Ninject to use a single instance of IDocumentStore and inject it into the constructor of each repository:

   13         public override void Load()

   14         {

   15             //check there is a connection string

   16             var connectionStringSection = ConfigurationManager.ConnectionStrings["LightsCameraAuctionRavenDB"];

   17             if (connectionStringSection == null || string.IsNullOrEmpty(connectionStringSection.ConnectionString))

   18                 throw new ApplicationException("no LightsCameraAuctionRavenDB connection string defined");

   19 

   20             Bind<AuctionService>()

   21                 .ToSelf()

   22                 .WithMetadata(MetadataBindingParamName, MetadataBindingParamValue);

   23 

   24             Bind<IDocumentStore>()

   25                 .ToMethod(ctx =>

   26                               {

   27                                 var store = new DocumentStore { ConnectionStringName = "LightsCameraAuctionRavenDB" };

   28                                   store.Initialize();

   29                                 return store;

   30                               })

   31                 .InSingletonScope();

   32 

   33             //..code omitted for brevity

   34         }

Below is the implementation of RavenDBBidderRepository:

    1 namespace LcaModel.RavenDB

    2 {

    3     using Interfaces;

    4     using Raven.Client;

    5 

    6     public class RavenDBBidderRepository : IBidderRepository

    7     {

    8         private IDocumentStore _documentStore;

    9 

   10         public RavenDBBidderRepository(IDocumentStore documentStore)

   11         {

   12             _documentStore = documentStore;

   13         }

   14 

   15         public void Add(LcaModel.Bidder bidder)

   16         {

   17             using (var store = _documentStore.OpenSession())

   18             {

   19                 store.Store(bidder);

   20                 store.SaveChanges();

   21             }

   22         }

   23     }

   24 }

As you can see its pretty trivial. At this point I ran the application but got the following error:

"An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information."

Because I downloaded the application and installed manually VisualStudio/Windows marks the code as suspect and won’t run it. There are different ways to resolve the problem but I opted for adding the following to app.config (lines 6,7,8):

    1 <?xml version="1.0"?>

    2 <configuration>

    3     <startup>

    4         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>

    5     </startup>

    6     <runtime>

    7         <loadFromRemoteSources enabled="true"/>

    8     </runtime>

    9     <connectionStrings>

   10         <add name="LightsCameraAuctionEntities" connectionString="metadata=res://*/EfLcaModel.csdl|res://*/EfLcaModel.ssdl|res://*/EfLcaModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Server=.\SQLEXPRESS;Database=LightsCameraAuction;  Integrated Security=true;MultipleActiveResultSets=True;&quot;" providerName="System.Data.EntityClient" />

   11         <add name="LightsCameraAuction" connectionString="Server=.\SQLEXPRESS;Database=LightsCameraAuction; Integrated Security=true;MultipleActiveResultSets=True"/>

   12         <add name="LightsCameraAuctionRavenDB" connectionString="Url=http://localhost:8080" />

   13     </connectionStrings>

   14 </configuration>

   15 

You’ll also notice the additional connection string for RavenDB. The other persistence layers I used all worked against SQL server but given that this is a NoSQL technology, it uses a different connection string.

So with the sandbox issue resolved I launch the application again – having only implemented the bidder repository so far the application errored with a NotImplementedException as expected. What I really wanted to do was see what data was being stored so I ran the application a few times before checking the database (document store). This is what it looked like:

non-sequential keys_thumb[3]

As you can see for each run of the application a set of sequential keys were added but the subsequent runs resulted in “jumps” of 32 (32 slots from each initial value). This is the default behaviour of RavenDB and is, like much of the RavenDB ecosystem configurable. What happens is that each session gets allocated a batch of keys so it can issue them without having to round trip to RavenDB each time, once its batch is used up it will be allocated another batch and so on. I didn’t have any particular need for sequential contiguous keys so chose not to override this behaviour. Examining the data for a given Bidder revealed a more serious problem:

default key property_thumb[6]

As you can see the BidderId is zero! The default behaviour of RavenDB is to assume that the property named Id is the key property of a given document. If the entity does not have a property named Id then it is added to the document store (although this is not easily visible to the consuming application). This is what has happened in the screenshot above. in order to customise this behaviour I added the following line to RavenDbModule:

   29   store.Conventions.FindIdentityProperty = prop => prop.Name == prop.DeclaringType.Name + "Id";

This tells RavenDB that the key properties are named TypeNameId, eg “AuctionId”. I ran the application again and examined the Bidder documents:

non-default key property_thumb[5]

As you can see the BidderId property has disappeared from the document, when saved or retrieved the $id document property is mapped to the BidderId property in the CLR object.

Next I implemented the RavenDBAuctionRepository, I ran the application again but got an unexpected error this time:

self-referencing loop_thumb[1]

What happened here was that RavenDB attempted to serialize the entire object graph using JSON.net and the exception was thrown because Auction has a reference to AuctionItem which in turn has a reference back to Auction and so on. I’ll mention this a bit more at the end of the post but the fix I used was to mark all the auction entities with the following attribute.

   10 [JsonObject(IsReference = true)]

   11 public class Bid

   12 {

The tells JSON.Net that if a class it is serializing contains a member of this type not to serialize it fully but rather to record only the key value of this property. I reran the application and got another NotImplementedException – I implemented the required methods in the Bid and Item repositories and reran the application:

complete_thumb[1]

Bingo. Same old same old, Barney wins again!

Looking at the contents of RavenDB we see this:

finished raven_thumb[1]

There are 3 pages of entities containing Auction,Item, Bidders and Bids for this auction.

In summary I have to say that I really like RavenDB and cant wait to try out in something a bit less lightweight. I haven’t covered any of the really cool stuff such as indexes, faceting etc. in this post but intend to in the future. I found it very easy to get started with and really liked the integration with .Net. One of the things that occurred to me while writing this post was that I would probably refactor the RavenDB layer to serialize only Auction and Bidder objects, in true DDD terms these are the aggregate roots and from the reading I’ve done on RavenDB this is the recommended approach to take. I decided not to include that refactoring in this post in order to maintain the same approach I used for the other persistence technologies.

  • (This will not appear on the site)