Mit Mango wird sich bezüglich Datenbanken für das Windows Phone einiges vereinfachen. Local Database wird Teil der Windows Phone OS 7.1 Plattform sein. Aktuell werden von der Plattform nicht viele Möglichkeiten, Daten zu persistieren, geboten.

Wer nicht auf Mango warten möchte oder kann, ist gezwungen, entweder die Daten per XML zu serialisieren, eine Binärserialisierung selbst zu implementieren, oder auf eine der mittlerweile verfügbaren Datenbanksysteme zurück greifen.

Gegenstand dieses Beitrags ist Sterling NoSQL OODB für .NET 4.0, Silverlight 4 und 5, Windows Phone 7. Dabei handelt es sich um eine kostenloses objektorientiertes Datenbanksystem mit voller LINQ to Object Unterstützung. Bedingt durch die angebotenen Möglichkeiten erschien es mir ratsam, sich dieses System genauer anzusehen – zumal auch die angepriesenen Performancezahlen vielversprechend sind (dazu jedoch mehr in einem weiteren Beitrag).

Sterling Installation und Einrichtung

Via CodePlex kann man sich wahlweise die Binaries oder aber auch den Sourcecode herunterladen. Zum aktuellen Zeitpunkt steht die Version 1.5 zur Verfügung. Wer sich den Sourcecode besorgt, findet zusätzlich Beispiele für die unterstützten Plattformen.

Für dieses Beispiel werden zwei Assemblies benötigt:

  • Wintellect.Sterling.WindowsPhone
  • Wintellect.Sterling.WindowsPhone.IsolatedStorage

Diese sind als Verweise einzubinden:

Sterling - Benötigte Referenzen

Damit ist alles an Vorbereitungsarbeiten erledigt, um mit der ersten Implementierung zu starten.

Sterling Datenbankengine einrichten

Damit Sterling verwendet werden kann, muss eine Engine instanziiert und aktiviert werden:

SterlingEngine databaseEngine = new SterlingEngine();
SterlingDefaultLogger defaultLogger = new SterlingDefaultLogger(SterlingLogLevel.Information);

//databaseEngine.SterlingDatabase.RegisterSerializer<CustomSerializer>();

databaseEngine.Activate();

Im ersten Schritt wird eine Instanz der SterlingEngine erstellt. Die Datenbank steht in weiterer Folge über die Eigenschaft SterlingDatabase zur Verfügung. Anschließend wird der SterlingDefaultLogger mit einem anzugebenden Log-Level erstellt. Über die Eigenschaft SterlingDatabase und der angebotenen Methode RegisterLogger können benutzerdefinierte Logger registriert werden. Schlussendlich muss die Engine aktiviert werden.

Ähnlich des Loggers kann auch ein eigener Serializer registriert werden. Beides (Logger und Serializer) müssen jedoch vor der Aktivierung der Engine registriert werden.

Damit ist die Engine grundsätzlich fertig eingerichtet und wir können dazu übergehen, unsere Datenbank und Tabelle einzurichten.

Datenbank einrichten und verwenden

Als nächstes registrieren wir eine Datenbank mit einer Tabelle, welche unsere Daten enthalten wird.

Bevor wir uns im Detail um die Datenbank kümmern, definieren wir die gewünschte Tabelle. Jeder Tabelleneintrag soll ein Buch darstellen. Die dafür notwendige Datenklasse kann so aussehen:

public class Book
{
public long Id { get; set; }

public string Title { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }

public BookCategory Category { get; set; }
public BookRating Rating { get; set; }

public string ISBN { get; set; }
}

public enum BookRating
{
Unrated = 0,
Bad = 1,
NotSoBad = 2,
Okay = 3,
Good = 4,
VeryGood = 5
}

public enum BookCategory
{
Unfiled = 0,
Dev = 1,
Fantasy = 2
}

Damit ist die Datenbank-Tabelle fertig.

Die Basis für eine Datenbank wird durch das Interface ISterlingDatabaseInstance gelegt. Darüber werden Möglichkeiten wie das Speichern, Löschen und Laden von Daten geboten (und natürlich noch vieles mehr). Als Basisklasse steht BaseDatabaseInstance zur Verfügung. Davon abgeleitet kann unsere erste Datenbank entstehen:

public class BookRepository : BaseDatabaseInstance
{
protected override List<ITableDefinition> RegisterTables()
{
return new List<ITableDefinition>
{
CreateTableDefinition<Book, long>(book => book.Id)
};
}
}

Schön zu sehen ist, dass die Methode RegisterTables überschrieben wird. Darüber wird eine Liste von Tabellen-Definitionen zurückgegeben, welche via CreateTableDefinition (stammt aus der Basisklasse) erstellt werden können. Im gezeigten Fall wird eine Tabellen-Definition für eine Entität Book mit einem Schlüssel vom Typ long definiert. Ebenfalls wird festgelegt, dass sich der Schlüssel in der Eigenschaft Id verbirgt. An dieser Stelle könnten bei Bedarf auch Indizes hinzugefügt werden.

Damit unsere Datenbank verwendet werden kann, muss diese registriert werden:

databaseEngine.SterlingDatabase.RegisterDatabase<BookRepository>
(
new IsolatedStorageDriver()
);

Für diesen Schritt ist die Methode RegisterDatabase aus der SterlingDatabase unserer Sterling-Engine aufzurufen. Als Typparameter wird die zu registrierende Datenbank übergeben. Der Methodenparameter (sofern angegeben) beschreibt den zu verwendenden Treiber für diese Datenbank.

Hier ist zu beachten, dass ein IsolatedStorageDriver-Objekt verwendet wird. Durch diesen Treiber werden die Daten in den Isolated Storage geschrieben. Wird kein Treiber angegeben, werden die Daten lediglich im Speicher gehalten. Bei Bedarf kann natürlich auch ein eigener Treiber implementiert und verwendet werden.

Die Engine ist hochgezogen, die Datenbank mit ihrer Tabelle ist registriert. Grundsätzlich steht alles zur Verwendung da. Möglicherweise gibt es aber noch Aktionen, auf die man reagieren möchte. Dies kann mit Hilfe von Triggern geschehen.

Einen Trigger einrichten

Mit Hilfe eines Triggers kann im Grunde auf drei Aktionen Einfluss genommen werden, die vor oder nach ihres Auftretens aufgerufen werden. Zur Verfügung stehen:

  • BeforeSave
  • AfterSave
  • BeforeDelete

Im Zuge des zugrunde liegenden Beispiels wird ein Trigger für das Hochzählen des eindeutigen Schlüssels benötigt. Als Basis steht die Klasse BaseSterlingTrigger<T, TKey> zur Stelle. Diese übernimmt zum einen das Modell (Tabelle), zum anderen den Typ des Schlüssels.

public class BookTrigger : BaseSterlingTrigger<Book, long>
{
private long NextId { get; set; }

public BookTrigger(long nextId)
{
NextId = nextId;
}

public override void AfterSave(Book instance)
{
// Nothing to do here for this sample
return;
}

public override bool BeforeDelete(long key)
{
// Nothing to do here for this sample
return true;
}

public override bool BeforeSave(Book instance)
{
if (instance.Id < 1)
instance.Id = NextId++;
return true;
}
}

Jegliche Trigger müssen direkt an der entsprechenden Datenbank registriert werden. Über die Schnittstelle ISterlingDatabaseInstance wird die Methode RegisterTrigger<T, TKey> vorgeschrieben.

long newId = 1
this.RegisterTrigger<Book, long>(new BookTrigger(newId));

this repräsentiert in diesem Fall eine Instanz vom Typ BookRepository.

Nun ist es so, dass BookTrigger für die Erzeugung einen Konstruktorparameter übergeben bekommt, welcher den nächsten zu vergebenden Schlüssel beschreibt. Für den ersten Aufruf mag nun obenstehender Code korrekt sein. Bei bestehenden Daten ist er das nicht mehr, was uns zum Thema führt, wie Abfragen durchgeführt werden können.

Abfragen durchführen, Laden, Speichern und Löschen

Sterling unterstützt LINQ to Objects. Um heraus zu finden, welcher Identifier der nächste wäre, kann dementsprechend so durchgeführt werden:

long newId = this.Query<Book, long>().Any() 
? (from id in this.Query<Book, long>() select id.Key).Max() + 1
: 1;

Auch in diesem Fall bezieht sich this auf die Instanz der Klasse BookRepository.

Mit nachfolgendem Code können beispielsweise alle Bücher mit der Id > 10 abgefragt werden:

var books = from book in this.Query<Book, long>()
where book.Key > 10
orderby book.Key descending
select book;

Zu beachten ist, dass Elemente vom Typ TableKey<TType, TKey> zurückgeliefert werden. TType repräsentiert hier den Typ der abzufragenden Klasse (Tabelle) und TKey den Schlüssel. Die Eigenschaft Key der erhaltenen Instanz repräsentiert den Schlüssel, die Eigenschaft LazyValue die komplette Instanz (in unserem Fall eine Instanz vom Typ Book).

Für Manipulationen stehen die Methoden

  • Load
  • Save
  • Delete

zur Verfügung.

Damit sollten nun die wichtigsten Möglichkeiten vermittelt sein, um mit Sterling arbeiten zu können.

Es empfiehlt sich, einen Blick in den Sterling User’s Guide zu werfen, da zahlreiche zusätzliche Möglichkeit á la Truncate, Purge etc. zur Verfügung stehen.

Engine herunterfahren und Ressourcen freigeben

Soll die Anwendung beendet werden, oder wird die Datenbank nicht mehr benutzt, sollten alle verwendeten Ressourcen wieder freigegeben werden. Hierzu implementiert die Klasse SterlingEngine das Interface IDisposable. Durch den Aufruf der Methode Dispose()  iteriert die Engine durch alle registrierten Datenbanken und gibt diese jeweils frei.

Besonderes Augenmerk darauf sollte bei der Verwendung unter Windows Phone gelegt werden.

Tipps für Sterling unter Windows Phone 7

Durch das Tombstoning beim Windows Phone empfiehlt es sich, die entsprechenden Events zu verwenden, um die Engine hochzuziehen, die Datenbanken zu registrieren, als alles auch wieder freizugeben.

Die Aktivierung sollte erfolgen in den Eventhandlern Application_Launching und Application_Activated. Die Freigabe sollte in Application_Deactivated und Application_Closing erfolgen.

Beim Aktivieren der Engine werden lediglich die Schlüssel und die Indizes in den Speicher geladen. Die Daten selbst werden nur bei Bedarf deserialisiert und geladen.

Download

Das Beispiel, aus dem obiger Code beispielhaft entlehnt wurde, findet sich nachfolgend. Zu beachten ist, dass obiger Code zum Großteil in anderer Form im Beispiel zu finden sein wird, da versucht wurde, dieses möglichst sauber abzubilden. Neben Sterling wird auf MVVM gesetzt, zusätzlich kommt auch MicroIoC als leichtgewichtiger IOC Container zum Einsatz.

Hinweis: Zu MicroIoC wird ein eigener Eintrag folgen, der genauer darauf eingeht, wie sich Sterling mit MicroIoC verbinden lässt und welcher Vorteil dadurch für eine Windows Phone Anwendung entsteht.

Über den Autor

Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler und Fotograf. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.