NoSQL mit RavenDB und ASP.NET MVC

nosql

Lange, lange Zeit (jedenfalls für mich Zwinkerndes Smiley ) galt, dass Daten ganz klar in eine Datenbank kommen. Die Daten werden gewöhnlich in einer relationalen Datenbank gespeichert und miteinander verknüpft. Seit einiger Zeit jedoch gibt es “Widerstand” – NoSQL ist das Stichwort.

Was sind NoSQL Datenbanken?

Es gibt verschiedenste Ausprägung von NoSQL Datenbanken – für einen ersten Eindruck ist der Wikipedia Artikel gut.

Prominente Vertreter sind MongoDB sowie CouchDB.

RavenDB?

RavenDB ist ein recht junger Vertreter der Dokumentendatenbanken. Der Quellcode ist Open Source, allerdings benötigt man für eine Closed-Source Anwendung eine Lizenz. Entwickelt ist es von Ayende Rahien, dessen Blog mittlerweile auch mit RavenDB als Datenquelle läuft.

Der größte Unterschied zu den anderen NoSQL Datenbanken liegt vermutlich in der guten Integration auf der Windows & .NET Plattform.

Datenspeicherung mit RavenDB…

image

Anders als bei einer SQL Datenbank werden in RavenDB schemalose JSON Dokumente gespeichert. Es können auch LINQ basierte Indexe angelegt werden. Die Dokumentation ist für einen ersten Einstieg auch recht gut. Insbesondere die “Document Structure Design Considerations” sollten Neulinge in NoSQL sich mal durchlesen.

Einstieg…

imageRavenDB kann über die Download-Seite runtergeladen werden. In diesem Package sind einige Samples, der RavenDB Server und diverse Client-Bibliotheken. Am interessantesten ist der Ordner “Server” und (jedenfalls für den Einstieg) der Ordner “Samples”.

Zum Starten von RavenDB gibt es mehrere Möglichkeiten:

- Als Konsolenanwendung, welche auf Port 8080 lauscht
- Als Webseite im IIS
- Als Windows Dienst

Vermutlich habe ich andere Optionen noch vergessen Zwinkerndes Smiley

Am einfachsten ist allerdings die Konsolenanwendung.

Dazu einfach im Ordner “Server” die “Raven.Server.exe” ausführen (als Admin!) :

image

RavenDB Management Oberfläche

Nach dem Start des Servers sollte sich die Management Oberfläche im Browser öffnen. Das RavenDB Management Studio ist in Silverlight umgesetzt und macht jedenfalls optisch einen netten Eindruck. In dieser Management Oberfläche kann man sich Dokumente anschauen und natürlich bearbeiten. Ich vermute einfach mal, dass es ähnlich ist wie das SQL Management Studio.

imageimage

image

Um RavenDB im Projekt nutzen zu können…

Kann man entweder die Assemblies aus dem Download Ordner nehmen oder man holt sich das NuGet Package.

Kommen wir zum Coding…

Nachdem nun die Konzepte wenigstens im Ansatz angeschnitten wurden und auch der Server läuft kommen wir nun zum Coding. Einige Code-Teile stammen aus dem Blogsystem vom Ayende – genannt Raccoon.

image

Aus dem Raccoon Projekt habe ich etwas Infrastruktur-Code mitgenommen.

Dieser Code erleichtert einfach den Zugriff auf die RavenDB und ist in jedem Controller einfach aufzurufen.

Nachfolgender Code ist “Infrastruktur”-Code. Wahrscheinlich kann man es auch kürzer fassen, aber dann wird es vermutlich nicht so elegant Zwinkerndes Smiley

 

 

 

RavenActionFilterAttribute.cs

Dieses Attribute ist in meinem Beispiel auch als Globaler Filter eingebunden in der Global.ascx

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

 

Der Code des Filters:

    /// <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

Im Grunde wird hier die Verbindung zum Store hergestellt und der Zugriff gewährleistet. Ich bin mir selber nicht ganz sicher, was er sonst noch macht 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 für alle Controller, welche auf die RavenDB zugreifen wollen

Um in den Controllern nun Zugriff auf die RavenDB zu bekommen, machen wir einen BaseController, welche als Property die IDocumentSession inne hat:

    public abstract class BaseController : Controller
    {
        public new IDocumentSession Session { get; set; }
    }

 

Bis hier hin ist der Code auch “Ayende”-geprüft, da er fast so aus dem Raccoon-Blog übernommen wurde – muss also bis hierhin auch gut sein Zwinkerndes Smiley

Ein einfaches Model

Ich hab ein sehr simples Model, welches auch keine “Verbindungen” zu anderen Objekten hat. Das folgt zu einem späteren Zeitpunkt.

    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 mit 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");
        }
    }

 

Alles in allem – total easy. Mal sehen wie es weitergeht…

Fazit oder TL;DR

Das wäre es erst mal zur Einführung von RavenDB. Wir haben nun eine kleine Infrastruktur, mit der man leicht an die RavenDB rankommt und die API erscheint erstmal recht simpel. Smiley  Der gesamte Soure Code steht auch auf Codeplex im BusinessBingo Repository zur Verfügung.

Weiterführende Links

Definitiv ist der Blog von Ayende zu empfehlen – dort finden sich einige RavenDB Posts. Desweiteren gibt es auch einige RavenDB Tutorials. Die Serie von Rob Ashton ist auch sehr zu empfehlen.


Kick It auf dotnet-kicks.de
Wenn dir der Blogpost gefallen hat, dann hinterlasse doch einen Kommentar. Wenn du auf dem Laufenden bleiben willst, abonniere unseren RSS Feed oder folge uns auf Twitter.

About the author

Written by Robert Mühsig

Robert Mühsig (@robert0muehsig) ist Webentwickler und beschäftigt sich mit Web-Frameworks (vor allem dem ASP.NET MVC Framework) und scheut sich auch nicht vor Javascript. Ansonsten bloggt er über all jene Probleme, die ihm über den Weg laufen. Seit 2008 ist er Microsoft MVP für ASP.NET und er arbeitet bei der T-Systems Multimedia Solutions GmbH in Dresden. Treffen kann man ihn online via Twitter (@robert0muehsig) oder dieser Seite oder bei der .NET User Group Dresden.

10 Responses

  1. Von Robert zu Robert ;)

    Abgesehen von “Der größte Unterschied zu den anderen NoSQL Datenbanken liegt vermutlich in der guten Integration auf der Windows & .NET Plattform.”, warum eigentlich RavenDB und nicht MongoDB?

    Ich finde MongoDB auch ziemlich interessant (zum einen auch, da die Community dahinter ziemlich groß und sehr aktiv ist), aber natürlich ist es auch keine Eierlegende Wollmilchsau ;)

    Leider kenne ich RavenDB zu wenig um einen qualifizierten Vergleich anzustellen.
    Mich würde aber Deine Meinung dazu sehr interessieren.
    Ein kleiner Vergleich (API-Geschwindigkeit) Deinerseits wäre auch nicht schlecht.

    Ansonsten aber, ein sehr guter Bericht über RavenDB!

    LG
    Robert Baminger

    Reply
    • Ich schau mal ob ich Zeit finde um mal MongoDB anzuschauen. Warum RavenDB? Ganz platt gesagt: Es ist vom Ayende – eigentlich kann es nicht schlecht sein und die Integration in Windows / .NET ist cool, auch wenn es diverse MongoDB C# Treiber gibt. Ich will damit eigentlich nur etwas rumexperimentieren. Rob Ashton hat einen netten Post zu diesem Thema geschrieben, der ein paar Vorzüge der einzelnen Systeme nennt: http://codeofrob.com/archive/2011/01/26/ravendb-vs-mongodb-why-i-dont-usually-bother.aspx

      Reply
  2. Hallo Robert,
    wäre noch interessant zu wissen wie sich die Performance im Vergleich mit den anderen (Relationalen) Datenbanken schlägt :)
    Gruß Sebastian

    Reply
    • Performance-Vergleiche sind immer schwierig, weil “kommt drauf an” die Königsantwort ist. Ich denke dadurch, dass man Daten redundant in unterschiedlichen Dokumenten lagert wird es von der Lesegeschwindigkeit bestimmt fix sein. Ayendes Blog ist ja auch echt fix. Ein direkter 1:1 Vergleich wird daher schwierig. Ayende hatte ja auch mal den MVC Music Store auseinander genommen und es nach RavenDB portiert.

      Reply
  3. @Robert Bamminger: wenn du des englischen mächtig bist, hör mal hier rein:
    http://herdingcode.com/?p=255

    Dort spricht ayende mit den 4 Jungs über seine DB. Ist zwar von Juni 2010, aber das meiste gilt noch. Kann gerade nicht reinhören, aber es wurde auch ein Vergleich zu MongoDB und CouchDb gezogen, und natürlich relationalen Datenbanken.

    Was ich noch im Kopf habe:
    - in vielem schneller als relationale Datenbanken; und das war Juni 2010
    - eine Query um eine Collection zu bekommen ist wohl quasi das Anlegen eines Index: im gegensatz zu relationalen Datenbanken kostet das nicht auf Dauer (jeder Index in einer relationalen Datenbank kostet Ressourcen und irgendwann auch Performance)
    - extreme Vorteile bei Verknüpfungen von tabellen, gerade multiplen Joins, um “Freunde von Freunden” u.ä. zu finden; weil die relationalen Datenbanken da einen Performanceeinbruch spätestens nahc dem 2. Join verzeichnen; daher sind NoSQL Datenbanken bei Leuten, die Netzwerke analysieren (Facebook anyone? oder alle andern big player) sehr beliebt
    - ein Linq-to-Raven Mapper ist drin, daher komfortabler als CouchDB/MongoDB; direkt für .NET gebaut, also wohl auch einfacher einzurichten
    - durch das fehlende Schema hast du den Vorteil, dass du es leichter hast zu migrieren
    - gleichzeitig ist das der grösste Nachteil: Datenkonsistenz ist schwer

    Man sollte aber auch beachten, dass man die Vorteile einer Dokumentdatenbank nutzt. Wenn man “nur” auf Performance aus ist, und einfach einen schnellen, persistierten key-value store will (mit “triggern” bzw. pub/sub um cross-domain daten zu synchronisieren), sollte man sich Redis anschaun, das benutzen die Jungs von Stackoverflow. Die Performance muss der Wahnsinn sein, und es gibt auch dort ne gute C# API dank http://www.servicestack.net/ , und mittlerweile auch webbasierte APIs.

    Reply
  4. mit webbasierten APIs meinte ich webbasierte admintools.. bin gerade etwas mischugge im Kopf. Und “Datenkonsistenz ist schwer” sollte “Datenintegrität zu gewährleisten ist aufwändiger” heissen.

    Reply

Comment on this post

Letzte Posts

  • image.png
    Chocolatey–apt-get für Windows

    Durch Zufall bin ich auf das Tool “Chocolatey” gestoßen. Wer die Website sich anschaut, wird evtl. eine Verwandschaft mit NuGet ausmachen. Was macht Chocolatey? Chocolatey ist ein “Maschine Package Manager”, das bedeutet, dass man für seine Maschine einfach Tools runterladen und Updaten kann – direkt über die Konsole. Was ist der Unterschied zu NuGet? NuGet ...

  • image.png
    SASS, LESS & Coffeescript in Visual Studio mit der Web Workbench

    CSS und Javascript sind die “kleinste” Schnittmenge von allen Browsern für die Erstellung von Web-Applikationen. Leider geht dabei etwas komfort verloren, daher lieben alle Webentwickler jQuery! SASS und LESS sind zwei Varianten, wie man “schöner” CSS schreiben kann und Coffeescript versucht Javascript Entwicklung zu vereinfachen. Aber immer der Reihe nach… Was ist SASS? SASS steht ...

  • image.png
    Code-Inside Sample nun auf GitHub: Google Code zu GitHub Migration

    Seit einiger Zeit habe ich Beispielcode auf Google Code bereitgestellt. Einfach nur noch weg von Google Code O-Ton damals war: Ich hatte mich für Google Code entschieden, weil ich hoffe dass früher oder später die Google Code Suche nutzbar ist und es dadurch wenigstens ein kleiner Mehrwert entsteht. Allerdings wirft es momentan noch ein Fehler. ...

  • image.png
    Windows-8-Hackathon @Night in Leipzig

    Hacken (=Entwickeln, nichts böswilliges!), Grillen und mitten in der Nacht fachsimpeln? Dann ist vielleicht der Windows-8-Hackathon was für dich. Der Hackathon wird vom 15. Juni (ab 19:00) bis zum 16. Juni (bis in die frühen Morgenstunden) in Leipzig stattfinden.  Mit dabei sind auch Darius Parys und Tom Wendel von der Microsoft Deutschland. Thematisch (wie der ...

  • image.png
    Einstieg in Redis on Windows & Redis mit .NET benutzen

    Redis gehört zu den NoSQL Datenbanken und ist dort in der Familie der Key-Value Stores zu finden. Redis wird oft mit “Blazing Fast” betitelt und laut dem Stackoverflow Thread soll es im Vergleich zu MongoDB zweimal (beim Schreiben) und sogar dreimal (beim Lesen) so schnell sein wie MongoDB – auch wenn der Vergleich etwas “hinkt” ...

Auf Amazon einkaufen & unterstützen

Facebook