Extending versioning metadata for RavenDB

Extending versioning metadata for RavenDB

RavenDB:s version bundle enables you to get built in versioning of all objects. These can easily be manipulated just like any other object and in this guide i will show an example on how to add useful metadata tags to the default objects.
The version bundle automatically add Version number and the Updated date. We will here add creator, created date, modified by and bring them all together in a base class that all our objects can then inherit. Note: This is just a mashup of RavenDB.net documentation and Ben Foster post.

Lets first create a Base entity.

namespace SampleSite.Core.Entities
{
    public abstract class BaseEntity
    {
        public string Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime RevisionDate { get; set; }
        public string CreatedBy { get; set; }
        public string RevisionBy { get; set; }
        public long? RevisionNr { get; set; }
    }
}

Now that we have an entity we want to use it to map the metadate to and from the document. For this we will need to use two listeners. We will use a IDocumentConversionListener to mainpulate the data while converting between document and entity. We will also use a DocumentStoreListener for the values we want to set at document creation(the convertion listener only trigger when working against existing documents).

So lets begin by creating the documentstore listener, wich will look like this;

using System;
using System.Web;
using Raven.Client.Listeners;
using Raven.Json.Linq;

namespace SampleSite.Data.Listeners
{
    public class StoreListener : IDocumentStoreListener
    {
        public bool BeforeStore(string key, object entityInstance, RavenJObject metadata, RavenJObject original)
        {
            if (!metadata.ContainsKey("Created-By"))
            {
                metadata["Active"] = true;
                metadata["Created-By"] = HttpContext.Current.User.Identity.Name;
                metadata["Created-Date"] = DateTime.UtcNow;
            }
            return true;
        }

        public void AfterStore(string key, object entityInstance, RavenJObject metadata) {  }
    }
}

Now what this does is to add the three new metatags to each new document. These will then be inherited by forthcoming versions that are created.
If you react to the HttpContext this is just a minimalistic example. A way of handling Identity injection, and how to test, can be found Bens blog wich i linked to above.

Now well create the Conversionlistener wich will look like the following:

namespace SampleSite.Data.Listeners
{
    public class ConversionListener : IDocumentConversionListener
    {
        public void EntityToDocument(string key, object entity, RavenJObject document, RavenJObject metadata)
        {
            metadata["Last-Modified-By"] = HttpContext.Current.User.Identity.Name;
            document.Remove("RevisionBy");
            document.Remove("RevisionDate");
            document.Remove("CreatedBy");
            document.Remove("CreatedDate");
            document.Remove("RevisionNr");
        }

        public void DocumentToEntity(string key, object entity, RavenJObject document, RavenJObject metadata)
        {
            if (entity is BaseEntity == true)
            {
                 var Item = entity as BaseEntity;
                 Item.RevisionDate = metadata.Value<DateTime>("Last-Modified");
                 Item.CreatedDate = metadata.Value<DateTime>("Created-Date");
                 Item.CreatedBy = metadata.Value<string>("Created-By");
                 Item.RevisionBy = metadata.Value<string>("Last-Modified-By");
                 Item.RevisionNr = metadata.Value<long>("Raven-Document-Revision");
        }
    }
}

Now all we have to do is to connect the listeners to our DocumentStore. In the earlier post i Used Ninject injection. This will show how to extend this to include our listeners. So open App_Data\NinjectWebCommon.cs and edit the IDocumentStoreBinding to look like this.

private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<IDocumentStore>().ToMethod(context =>
            {
                var documentStore = new DocumentStore { ConnectionStringName = "RavenDB" };
                documentStore.RegisterListener(new StoreListener());
                documentStore.RegisterListener(new ConversionListener());
                return documentStore.Initialize();
            }).InSingletonScope();
            kernel.Bind<IDocumentSession>().ToMethod(context => context.Kernel.Get<IDocumentStore>().OpenSession()).InRequestScope();
        }        

And were done. I recommend checking out Bens post regarding injecting the Username. If you have any other nice solution to it then feel free to post a comment!

Leave a Reply

Your email address will not be published. Required fields are marked *