NoSQL mit RavenDB und ASP.NET MVC
Lange, lange Zeit (jedenfalls für mich
) 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…
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…
RavenDB 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 ![]()
Am einfachsten ist allerdings die Konsolenanwendung.
Dazu einfach im Ordner “Server” die “Raven.Server.exe” ausführen (als Admin!) :
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.
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.
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 ![]()
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 ![]()
/// <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 ![]()
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.
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.







Robert Baminger
5. July 2011
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
Robert Mühsig
6. July 2011
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
Sebastian
5. July 2011
Hallo Robert,
wäre noch interessant zu wissen wie sich die Performance im Vergleich mit den anderen (Relationalen) Datenbanken schlägt
Gruß Sebastian
Robert Mühsig
6. July 2011
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.
Hannes Kochniß
6. July 2011
@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.
Hannes Kochniß
6. July 2011
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.