Ich habe in den letzten Tagen ein Wenig mit der NoSQL (übersetzt mit “not only sql”) Datenbank “RavenDB” herumgespielt. RavenDB ist eine Dokument-Datenbank. Das bedeutet in diesem Fall, dass die Daten nicht in Form von relationalen Tabellen, sondern in Form von JSON-Dokumenten gespeichert werden. In dieser Datenbank können Objekte “einfach so” ohne kompliziertes Mapping gespeichert werden.
Einige Vorteile bzw. Eigenarten von Dokument-basierten Datenbanken sind:
- die DB muss kein festgelegtes Schema haben, da alles in Dokumenten gespeichert wird – die Dokumente an sich bilden das Schema
- es muss kein Mapping von Klassen auf Datenbankobjekte vorgenommen werden
- Performance-Vorteile beim schreibenden Zugriff, da die Konsistenz innerhalb der Datenbank nicht immer gewährleistet sein muss
- große Datenbestände können auf mehrere kleine Server verteilt werden
Zu finden ist RavenDB unter http://ravendb.net/. Lizenziert wird die Datenbank “dual”, also frei für OpenSource-Projekte und kostenpflichtig für kommerzielle closed-source Software.
Einführung
Nun aber zur Datenbank an sich: Die Datenbank kann grundsätzlich in zwei verschiedenen Modi betrieben werden – embedded in der eigenen Anwendung oder als Server. Unterschied sind die einzubindenden Referenzen und die Einrichtung der Verbindung zur Datenbank. Wichtig zu wissen ist, dass die Embedded-Variante nur mit Anwendungen, die für das .NET Framework 4 kompiliert werden, funktioniert. Die Client-Server Variante funktioniert auch mit .NET 3.5. Die entpackte RavenDB hat diese Verzeichnisstruktur:
Um nun den Server zu starten genügt das Starten der Raven.Server.Exe im Verzeichnis “Server”. Der Server ist dann unter http://localhost:8080 zu erreichen und wartet mit einer kleinen Administrationsoberfläche unter der man sich die gespeicherten Dokumente anschauen kann, Statistiken einsehen kann und Map/Reduce Indizes anlegen kann – aber dazu später mehr.
RavenDB – Hello World Skiverleih – ein kleines Tutorial
Nun erst einmal zu einem kleinen “Hello-World” mit RavenDB. Nehmen wir an, wir betreiben einen Skiverleih (diesmal keine Autovermietung :-)) und wollen unsere Ski in der Datenbank ablegen und dazu noch Bewertungen der Ski unserer Kunden…
Also zunächst ein neues Visual Studio Projekt geöffnet, ich nehme hier eine Konsolenanwendung und den RavenDB-Client aus dem Verzeichnis Client eingebunden.
Die Ski werden durch die Klasse Ski repräsentiert.
public class Ski
{
public string Id { get; set; }
public decimal VerleihpreisTag { get; set; }
public decimal Anschaffungspreis { get; set; }
public List<Bewertung> Bewertungen { get; set; }
public int LaengeInCm { get; set; }
public bool Gewachst { get; set; }
public Ski()
{
Bewertungen = new List<Bewertung>();
}
public double Durchschnittswertung
{
get
{
if (Bewertungen.Count > 0)
return Bewertungen.Average(x => x.Wertung);
else
return 0;
}
}
}
Für die Datenbank und das “Wiederfinden” bestimmter Ski ist es notwendig, dass die Klasse ein Property mit dem Namen “Id” besitzt. Für die Erstellung von IDs in RavenDB gibt es drei Möglichkeiten:
- explizites Festlegen – also bspw. durch mySki.Id = “1”
- automatische Generierung – wird der Klasse kein Id-Property “spendiert”, dann generiert RavenDB beim Einfügen automatisch eine ID (GUID), wenn es also nicht wichtig ist konkrete Ski anhand ihrer ID wiederzufinden, kann auf die Id verzichtet werden
- teilautomatische Generierung – die Id kann bei allen Instanzen der Ski-Klasse auf bspw. “ski/” gesetzt werden. Das veranlasst RavenDB dazu die Ids aufsteigend zu generieren, also “ski/1” für den ersten Ski, “ski/2” für den zweiten, etc.
Im Beispiel werde ich die Ids manuell vergeben.
Nun gibt es noch die Klasse Bewertung, die eine Wertung und einen Kommentar des Skifahrers enthält.
public class Bewertung
{
public double Wertung { get; set; }
public string Kommentar { get; set; }
}
Innerhalb der Ski-Klasse werden alle Bewertungen zu einem Ski als List<Bewertung> gespeichert und ein Property Durchschnittswertung errechnet den Durchschnitt aller Bewertungen des Ski.
Um nun Daten in RavenDB zu speichern, muss zuerst ein DocumentStore angelegt, instanziert und initialisiert werden:
//Initialisierung
var ds = new DocumentStore() { Url = "http://localhost:8080" };
ds.Initialize();
Hier genügt es die URL, unter der die Datenbank läuft anzugeben. Möchte man den Embedded Client verwenden, kann hier auch das Datadirectory angegeben werden, unter dem die Daten gespeichert werden sollen.
Nun erstelle ich ein paar Ski mit Bewertungen:
//ein Ski mit zwei Bewertungen
var mySki = new Ski();
mySki.Id = "Ski/1";
mySki.VerleihpreisTag = 11.0m;
mySki.Anschaffungspreis = 399.99m;
mySki.LaengeInCm = 170;
mySki.Gewachst = true;
mySki.Bewertungen.Add(new Bewertung() { Wertung = 5, Kommentar = "Super Ski!" });
mySki.Bewertungen.Add(new Bewertung() { Wertung = 2, Kommentar = "Genial am Hang!" });
//noch ein Ski mit einer Bewertung ungewachst
var mySki2 = new Ski();
mySki2.Id = "Ski/2";
mySki2.VerleihpreisTag = 9.0m;
mySki2.Anschaffungspreis = 299.99m;
mySki2.LaengeInCm = 120;
mySki2.Gewachst = false;
mySki2.Bewertungen.Add(new Bewertung() { Wertung = 1, Kommentar = "Doof!" });
//noch ein Ski mit einer Bewertung ungewachst
var mySki3 = new Ski();
mySki3.Id = "Ski/3";
mySki3.VerleihpreisTag = 9.0m;
mySki3.Anschaffungspreis = 299.99m;
mySki3.LaengeInCm = 120;
mySki3.Gewachst = false;
mySki3.Bewertungen.Add(new Bewertung() { Wertung = 2, Kommentar = "Blöd!" });
Wie angekündigt habe ich die IDs manuell vergeben. Würde ich nun einen weiteren Ski mit der Id “Ski/” anlegen wäre seine Id automatisch “Ski/4”.
Speichern in der Datenbank
Zum Speichern der Ski in der Datenbank genügt folgender Code:
//Ski speichern
using (var session = ds.OpenSession())
{
session.Store(mySki);
session.Store(mySki2);
session.Store(mySki3);
session.SaveChanges();
}
Hier wird eine Session mit der Datenbank geöffnet, die Ski gespeichert und SaveChanges() aufgerufen. SaveChanges() entspricht in etwa einem COMMIT in einer SQL-Datenbank.
Die gespeicherten Ski können nun auch direkt in der Datenbank angezeigt werden. Also http://localhost:8080 und Klick auf “Documents” ergibt folgendes Bild:
Die drei Ski sind also gespeichert – soweit so gut :-) Schauen wir uns einmal den Ski mit der ID “Ski/1” genauer an, um den Aufbau innerhalb der Datenbank zu sehen:
Der Ski wird also in einem Dokument inkl. aller Bewertungen gespeichert. Unter Document Metadata sind noch die von RavenDB vergebenen Metadaten enthalten. Interessant hier ist der “Raven-Entitiy-Name”, der als Plural der Klasse benannt ist.
Ändern in der Datenbank
Um nun an einzelnen Ski, wieder im Beispiel “Ski/1” heranzukommen, um mit diesem weiter arbeiten zu können bzw. um die Daten zu verändern ist folgender Code notwendig:
//einen Ski lesen und ändern
using (var session = ds.OpenSession())
{
//lesen
var myDbSki = session.Load<Ski>("Ski/1");
//ändern
myDbSki.VerleihpreisTag = 10.0m;
//speichern
session.Store(myDbSki);
session.SaveChanges();
}
Also wieder eine Session öffnen, den Ski per Load<Ski>("<Id_des_Ski>”) laden, verändern und per Store() und SaveChanges() wieder in der Datenbank speichern.
Löschen eines Ski
Soll nun ein Ski wieder aus der Datenbank gelöscht werden, muss dieser zunächst gelesen werden, um eine Verknüpfung zu einer existierenden Instanz herzustellen.
//einen Ski löschen
using (var session = ds.OpenSession())
{
session.Delete<Ski>(session.Load<Ski>("Ski/2"));
session.SaveChanges();
}
Soweit erst einmal zu den Grundfunktionen von RavenDB. Im nächsten Post werde ich die Indexierung via Map/Reduce und die Abfrage mehrerer Instanzen vorstellen.