MongoDB: Herausforderungen und Lösungsansätze für die .NET-Serialisierung

Versionsvielfalt von NoSQL-Dokumenten (Teil 2)
Kommentare

Zusätzliche Felder ignorieren
Der einfachste Weg, alle Dokumente aus der Collection lesen zu können, besteht darin, unbekannte Felder zu ignorieren. Entscheidet man sich für diese Option, werden alle

Zusätzliche Felder ignorieren

Der einfachste Weg, alle Dokumente aus der Collection lesen zu können, besteht darin, unbekannte Felder zu ignorieren. Entscheidet man sich für diese Option, werden alle Eigenschaften verworfen, die nicht auf Objekte übertragen werden können. Dazu kann man auf der entsprechenden Klasse das BsonIgnoreExtraElements-Attribut setzen:

 [BsonIgnoreExtraElements]
public class Person
{
...  

Windows Developer

Der Artikel „Versionsvielfalt von NoSQL-Dokumenten“ von Christian Heger und Daniel Weber ist erstmalig erschienen im Windows Developer 8.2012

Nach dieser Änderung treten keine Ausnahmen mehr auf. Allerdings werden die Adressdaten nicht richtig aus Dokumenten im neuen Format gelesen. Solange die Applikation mit diesen zusätzlichen Daten nicht arbeitet, wäre dieses Verhalten ja noch akzeptabel. Wird ein so gelesenes Dokument allerdings gespeichert, gehen die zusätzlichen Eigenschaften allerdings auch in der Datenbank verloren. Beim Speichern von Objekten ersetzt der MongoDB-Treiber nämlich das gesamte Dokument mit der passenden ID. Und da es kein festes Schema gibt, wird das Format des aktuell vorhandenen Objekts verwendet – und das kennt die zusätzlichen Felder nicht mehr.

Nicht nur Attribute

Neben der Verwendung von Attributen gibt es die Möglichkeit, Serialisierungsoptionen bei der Initialisierung des MongoDB-Treibers in Code zu setzen. Auf diese Art tritt keine Vermischung von Domänenobjekten und Persistenzlogik auf.

Zusätzliche Felder auffangen

Alle Felder, die nicht Eigenschaften der Klasse zugeordnet werden, können in der Form eines BsonDocument aufgefangen werden. Dazu definiert man eine Eigenschaft vom Typ BsonDocument, die mit dem Attribut BsonExtraElements versehen wird. Der wichtigste Effekt ist, dass keine Daten aus dem Originaldokument verloren gehen, selbst wenn sich zusätzliche und unbekannte Felder in dem Dokument befinden. Eine verlustfreie Umwandlung vom Dokument zum Objekt und zurück wird ermöglicht.

BsonDocument ist eine direkte Repräsentation eines MongoDB-Dokuments. Es ermöglicht den Zugriff auf die Felder eines Dokuments, ohne dass dieses in eine .NET-Klasse serialisiert wird. Das macht die Arbeit mit ihm recht unkomfortabel, aber flexibel. Das Beispiel in Listing 4 zeigt, wie ein BsonDocument verwendet werden kann, um im Bedarfsfall aus einer der beiden Versionen des Person-Dokuments auszulesen. Solange ein Feld City vorhanden ist, wird dieses direkt eingelesen. Sollte dieses Feld allerdings leer sein, wird auf das BsonDocument zugegriffen, um die Eigenschaft des eingebetteten Address-Dokuments zu lesen.

Listing 4: BsonExtraElements

public class Person 
{
    private string _city;

    [BsonExtraElements]
    public BsonDocument CatchAll { get; set; }

     private BsonDocument Address
       {
           get
           {
               var address = CatchAll.GetElement("Address");
               return address.Value.AsBsonDocument;
           }
       }

       public string City
       {
           get
           {
               return _city ?? Address.GetElement("City").Value.AsString;
           }
           set 
           {
               _city = value;
           }
       }
// more properties ...
}  

Ein wesentlicher Nachteil bei dieser Lösung ist allerdings, dass das Person-Objekt mit Code belastet wird, der spezifisch für die Persistenz ist. Die Objekte werden komplexer, und der Fokus verschiebt sich von der Darstellung eines Geschäftsobjekts zur Lösung datenbankbezogener Probleme. Besser wäre es, die Versionierung von Dokumenten in einer ausgelagerten Klasse zu behandeln.

Benutzerdefinierte Serializer

Indem man einen angepassten Serializer für eine Klasse zur Verfügung stellt, kann man vollständig die Kontrolle darüber übernehmen, wie sich diese als Dokument darstellt. Um verschiedene Versionen von Dokumenten zu unterstützen, reicht es, die Deserialisierung anzupassen, also die Umwandlung des Dokuments zum Objekt. Auf dem umgekehrten Weg soll gerade keine Anpassung stattfinden, da immer die aktuellste Version des Dokuments in die Datenbank geschrieben werden soll (Abb. 1)

Versionsumwandlung durch benutzerdefinierten Serializer
Versionsumwandlung durch benutzerdefinierten Serializer

Sobald auf ein Dokument mit der alten Struktur zugegriffen wird, kann der Serializer dafür sorgen, dass das Dokument in die neue Dokumentenstruktur überführt wird. Der Serializer aktualisiert immer nur ein Dokument. Wird ein altes Dokument nie gespeichert, verbleibt es in seinem originalen Format. Es wird also innerhalb einer Collection verschiedenste Versionsstände geben. Ein Serializer muss mit jeder Version umgehen können. Am besten lässt sich das durch die Verkettung mehrerer Serializer erreichen, von denen jeder für eine bestimmte Dokumentenversion verantwortlich ist. Kommt eine neue Version hinzu, wird ein weiterer Schritt hinzugefügt.

Dem Serializer sind aber auch Grenzen gesetzt. Möchte man die Dokumentenstruktur so ändern, dass ein Dokument auf mehrere Collections aufgeteilt wird, lässt sich das durch einen Serializer nicht bewerkstelligen. Hier muss man die entsprechende Logik in ein Repository einbauen.

Fazit

Mithilfe der Serialisierung des MongoDB-Treibers wird die Brücke zwischen der schemalosen NoSQL-Welt und der typisierten objektorientierten Welt geschlagen. Der gut konfigurierbare Serializer erlaubt die einfache und gut kontrollierbare Überführung flexibler Dokumente in Objektstrukturen. Benutzerdefinierte Serializer (Listing 5) bieten ein mächtiges Werkzeug, um komplexe Umformungen vorzunehmen.

Listing 5: Benutzerdefinierter Serializer

public class MigratingCustomerSerializer : ICustomerSerializerHandler 
{
    public ICustomerSerializerHandler Next { get; set; }
    
    public Customer HandleObject(BsonDocument document) 
    {
        BsonElement addressElement;
        if (!document.TryGetElement("Address", out addressElement)) 
        {
            return new Person 
            {
                _id = document.GetElement("_id").Value.ToString(),
                FirstName = document.GetElement("FirstName").Value.ToString(),
                LastName = document.GetElement("LastName").Value.ToString(),
                Address = new Address
                {
                    Street = document.GetElement("Street").Value.ToString(),
                    City = document.GetElement("City").Value.ToString(),
                    Country = document.GetElement("Country").Value.ToString(),
                    ZipCode = document.GetElement("ZipCode").Value.ToInt32(),
                }
            };
        }
        return this.Next.HandleObject(document);
    }
}  

Christian Heger ist Lead Software Architect bei der Zühlke Engineering GmbH. Dort baut er großartige Anwendungen mit agilen Teams. Er programmiert in C# und JavaScript. Sein Hauptinteresse sind responsive und skalierbare Webanwendungen.
Daniel Weber (B. Eng.) arbeitet seit 2010 bei der Firma Zühlke Engineering GmbH als Software Engineer. Er arbeitet vorwiegend mit .NET-Technologien.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -