31 August 2011 NoSQL; RavenDB; ASP.NET MVC CI Team

 

For a loooong time (at least for me Zwinkerndes Smiley ) it was a fact that files have to be in a database. Usual files are saved in a relational database and linked. But in a while there exists resistance – NoSQL is the word.

What are NoSQL database?

There are several types of NoSQL database – for a first view I recommend you the Wikipedia article.

Prominent representatives are MongoDB and CouchDB.

RavenDB?

RavenDB is a quite young representative of the document database. The Quellcode is Open Source but you need a Closed-Source application licence. The developer is Ayende Rahien which Blog works with RavenDB as data source as well.

The main different to the other NoSQL databases is the well done integration into Windows & .NET platforms.

Data storage with RavenDB…

image

Different to an SQL database schema less JSON document will be saved in RavenDB. It’s also possible to apply LINQ based Indexes. The documentation is also quite good for a first entry. At least first-time user should read “Document Structure Design Considerations”.

Introduction…

image1290RavenDB could be downloaded at the Download-side. In this package are several Samples, the RavenDB server and various Client-libraries included. The most interesting folder is the “Server” folder and (at least for the introduction) the folder “Samples”.

To start RavenDB there are various options:

Probably I forget something Zwinkerndes Smiley

The easiest way is the console application.

All you have to do is run the “Raven.Server.exe” in the folder “Server” (as admin!):

image

RavenDB management interface

After the start of the server should the management interface open in the browser. The RavenDB management studio is in Silverlight and it looks quite nice. In this management interface it’s possible to take a look at the documents and edit them. I think it’s related to the SQL management studio.

imageimage

image

To use RavenDB in projects…

You can get the assemblies from the download folder or you get the NuGet package.

Now to the Coding…

After we’ve talked about the concepts and the server works we are now going to talk about the Coding. Some Code-parts are from the Blogsystem of Ayende – named Raccoon.

I’ve taken some Infrastructure-code from the Raccoon project.

This code makes it easier to get access to the RavenDB and it’s easy to call from every Controller.

The following code is “Infrastructure” Code. Maybe it’s possible to make it shorter but then it’s not that classy Zwinkerndes Smiley

RavenActionFilterAttribute.cs

The attribute in my example is also integrated as Global Filter in the Global.ascx

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new RavenActionFilterAttribute());
        }

The code of the filter:

/// <summary>
    /// This filter will manage the session for all of the controllers that needs a Raven Document Session.
    /// It does so by automatically injecting a session to the first public property of type IDocumentSession available
    /// on the controller.
    /// </summary>
    public class RavenActionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.IsChildAction)
            {
                DocumentStoreHolder.TrySetSession(filterContext.Controller, (IDocumentSession)filterContext.HttpContext.Items[this]);
                return;
            }
            filterContext.HttpContext.Items[this] = DocumentStoreHolder.TryAddSession(filterContext.Controller);
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.IsChildAction)
                return;
            DocumentStoreHolder.TryComplete(filterContext.Controller, filterContext.Exception == null);
        }
    }

DocumentStoreHelper.cs

In fact this is where the connection to the store and the access will be warranted. I’m not sure what else the function is Zwinkerndes Smiley

 
    /// <summary>
    /// This class manages the state of objects that desire a document session. We aren't relying on an IoC container here
    /// because this is the sole case where we actually need to do injection.
    /// </summary>
    public class DocumentStoreHolder
    {
        private static IDocumentStore documentStore;

        public static IDocumentStore DocumentStore
        {
            get { return (documentStore ?? (documentStore = CreateDocumentStore())); }
        }

        private static IDocumentStore CreateDocumentStore()
        {
            var store = new DocumentStore
            {
                ConnectionStringName = "RavenDB"
            }.Initialize();

            return store;
        }

        private static readonly ConcurrentDictionary<Type, Accessors> AccessorsCache = new ConcurrentDictionary<Type, Accessors>();

        private static Accessors CreateAccessorsForType(Type type)
        {
            var sessionProp =
                type.GetProperties().FirstOrDefault(
                    x => x.PropertyType == typeof(IDocumentSession) && x.CanRead && x.CanWrite);
            if (sessionProp == null)
                return null;

            return new Accessors
            {
                Set = (instance, session) => sessionProp.SetValue(instance, session, null),
                Get = instance => (IDocumentSession)sessionProp.GetValue(instance, null)
            };
        }

        public static IDocumentSession TryAddSession(object instance)
        {
            var accessors = AccessorsCache.GetOrAdd(instance.GetType(), CreateAccessorsForType);

            if (accessors == null)
                return null;

            var documentSession = DocumentStore.OpenSession();
            accessors.Set(instance, documentSession);

            return documentSession;
        }

        public static void TryComplete(object instance, bool succcessfully)
        {
            Accessors accesors;
            if (AccessorsCache.TryGetValue(instance.GetType(), out accesors) == false || accesors == null)
                return;

            using (var documentSession = accesors.Get(instance))
            {
                if (documentSession == null)
                    return;

                if (succcessfully)
                    documentSession.SaveChanges();
            }
        }

        private class Accessors
        {
            public Action<object, IDocumentSession> Set;
            public Func<object, IDocumentSession> Get;
        }

        public static void Initailize()
        {
            //RavenProfiler.InitializeFor(DocumentStore,
            //    //Fields to filter out of the output
            //    "Email", "HashedPassword", "AkismetKey", "GoogleAnalyticsKey", "ShowPostEvenIfPrivate", "PasswordSalt", "UserHostAddress");

        }

        public static void TrySetSession(object instance, IDocumentSession documentSession)
        {
            var accessors = AccessorsCache.GetOrAdd(instance.GetType(), CreateAccessorsForType);

            if (accessors == null)
                return;

            accessors.Set(instance, documentSession);
        }
    }

BaseController for all Controllers which want to access on the RavenDB

To get access to the RavenDB on the Controller we will make a BaseController which has all iDocumentSessions as Property:

Till this part the Code is “Ayende” checked because it’s from the Racoon-Blog – so it should be good Zwinkerndes Smiley

An easy model

I’ve chosen a very simple model without any connections to other objects. This will follow later on.

public class Word
    {
        public Guid Id { get; set; }
        public string Value { get; set; }
        public string Description { get; set; }
        public string LcId { get; set; }
        public int UpVotes { get; set; }
        public int DownVotes { get; set; }
    }

CRUD with RavenDB

public class WordsController : BaseController
    {
        public ViewResult Index()
        {
            var words = Session.Query<Word>().ToList();
            return View(words);
        }

        //
        // GET: /Words/Details/5

        public ViewResult Details(Guid id)
        {
            Word word = Session.Load<Word>(id);
            return View(word);
        }

        //
        // GET: /Words/Create

        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /Words/Create

        [HttpPost]
        public ActionResult Create(Word word)
        {
            if (ModelState.IsValid)
            {
                Session.Store(word);
                Session.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(word);
        }

        //
        // GET: /Words/Edit/5

        public ActionResult Edit(Guid id)
        {
            Word word = Session.Load<Word>(id);
            return View(word);
        }

        //
        // POST: /Words/Edit/5

        [HttpPost]
        public ActionResult Edit(Word word)
        {
            if (ModelState.IsValid)
            {
                Session.Store(word);
                Session.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(word);
        }

        //
        // GET: /Words/Delete/5

        public ActionResult Delete(Guid id)
        {
            Word word = Session.Load<Word>(id);
            return View(word);
        }

        //
        // POST: /Words/Delete/5

        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(Guid id)
        {
            Word word = Session.Load<Word>(id);
            Session.Delete(word);
            Session.SaveChanges();
            return RedirectToAction("Index");
        }
    }

All in all – totally easy. Let’s see how it goes on…

Result

That’s it about the introduction to RavenDB. This is just a small infrastructure that makes it easy to get in touch with RavenDB. You will find the whole Source Code on Codeplex in BusinessBingo Repository.

Links

Of course I recommend you the blog of Ayende – there are some posts of RavenDB. Also there are some RavenDB tutorials. And if that’s not enough take a look on the page of Rob Ashton.