Posts mit dem Label XML-Serialisierung werden angezeigt. Alle Posts anzeigen
Posts mit dem Label XML-Serialisierung werden angezeigt. Alle Posts anzeigen

Donnerstag, 22. August 2013

Generische Datenspeicherung mit XML - Teil 3 (XML Datei als Generic Entity Data Storage)

Bisherige Teile dieser Lecture

Falls Sie die bisherigen Teile dieser Lecture noch nicht kennen:

Kurze Rekapitulation was wir machen wollen

Wir wollen also einen ersten Generic Entity Data Storage implementieren, in welchem Generic Entities gespeichert werden sollen. Als Speicherort des Data Storage wählen wir für diesen Teil der Lecture eine XML-Datei. Jetzt schreiben wir also zum ersten mal so richtig Code für unsere Komponente :-)

Übersicht zur geplanten Implementierung

Für die Implementierung des Data Storage benötigen wir genau eine Klasse: den GenericEntityXmlFileDataStorage.
Diese muss dann das Interface IGenericEntityDataStorage implementieren. Die einzige Information, welche Sie benötigt, ist noch der physikalische Speicherort der Datei inklusive des Dateinamens. Da diese Angabe wesentlich ist für ihr ordnungsgemäßes Funktionieren, werden wir den Dateinamen bereits im Konstruktor als Argument vorsehen

Der eigentliche Umgang mit den Daten in der Datei - sprich das Speichern, Finden und Löschen - werden wir mit der LINQ to XML Technologie von .Net implementieren - im Folgenden mehr dazu. Hier jetzt erst einmal die Definition der Klasse GenericEntityXmlFileDataStorage:

Wichtige weitere Frage - wie soll das XML innerhalb der Datei formatiert sein? Hier zunächst einmal zusammengefasst, welche Daten wir speichern wollen: Generic Entities mit den String-Eigenschaften "ApplicationKey", "FullClassName", "EntityId" sowie einem XElement "XmlSerialisierung". Basierend hierauf werden wir ein XML-Schema nehmen, welches dem folgenden Beispiel-XML-Code entspricht:

<?xml version="1.0" encoding="utf-8" standalone="yes"?> <GenericEntities> <GenericEntity EntityId="17" ApplicationKey="MyTestApp" FullClassName="foo.User"> [Hier steht der serialisierte xml code des Users mit ID 17] </GenericEntity> <GenericEntity EntityId="18" ApplicationKey="MyTestApp" FullClassName="foo.User"> [Hier steht der serialisierte xml code des Users mit ID 18] </GenericEntity> </GenericEntities>

Implementierung mit LINQ to XML

Konstruktor

Als erstes betrachten wir den Konstruktor. In diesem legen wir die Datei an, sofern sie noch nicht existiert. Dann laden wir den Datei-Inhalt in ein Objekt der Klasse XDocument

An dieser Stelle einen Hinweis zur Implementierung: Wir werden immer nur komplette GenericEntity-Blöcke innerhalb des XDocuments bearbeiten. Einen kompletten Block einfügen, austauschen oder löschen. Wir werden keine Inhalte der Blöcke bearbeiten. Das macht uns das Arbeiten leichter. Wir können hierzu auf das oberste XElement mit dem Namen "GenericEntities" (siehe oben) aus dem XDocument zugreifen - dieses bietet dann alle benötigen gerade genannten Operationen an

Hier die wesentlichen Code-Segmente des Konstruktors:

if (!File.Exists(fileName)) { using (FileStream fileStream = File.Open(fileName, FileMode.CreateNew)) { using (XmlWriter writer = XmlWriter.Create(fileStream)) { writer.WriteStartDocument(true); writer.WriteWhitespace(Environment.NewLine); writer.WriteStartElement("GenericEntities"); writer.WriteEndElement(); writer.Flush(); } } } XDocument Document = XDocument.Load(fileName); var genericEntitiesNodes = from c in Document.Descendants() where c.Name.LocalName == "GenericEntities" select c; foreach (var genericEntitiesNode in genericEntitiesNodes) { XElement GenericEntitiesNode = genericEntitiesNode; break; }

Natürlich sollte es nur 1 XElement "GenericEntities" im XML geben. Die Abfrage mit LINQ to XML gibt allerdings immer eine Liste zurück. Wir nehmen einfach immer das erste Element der Liste ...

Hilfsmethoden

Im nächsten Schritt implementieren wir dann eine Hilfsmethode um ein Generic Entity mithilfe seiner ID-Eigenschaften im XML finden

Hier die wesentlichen Code-Segmente um ein Generic Entity XElement zu finden:

var existingItems = from c in GenericEntitiesNode.Descendants() where c.Name.LocalName == "GenericEntity" && c.Attribute("EntityId").Value == entityId && c.Attribute("ApplicationKey").Value == applicationKey && c.Attribute("FullClassName").Value == fullClassName select c; foreach (XElement existingItem in existingItems) { return existingItem; } return null;

Auch hier wieder - es sollte immer nur 1 Element geben. Die Abfrage mit LINQ to XML gibt allerdings immer eine Liste zurück. Wir nehmen einfach immer das erste Element der Liste ...

Speichern, Finden und Löschen

Beim den eigentlichen Hauptoperationen des Data Storage - dem Speichern, Finden und Löschen - greifen wir hauptsächlich auf die gerade vorgestellte Hilfsmethode zu. Außerdem benutzen wir die Methoden des Top-XElement "GenericEntitiesNode" um die Operationen durchzuführen. Anschließend machen wir die Änderungen mittels der Save-Methode des XDocuments persistent

Hier die wesentlichen Code-Segmente zum Speichern einer Generic Entity:

XElement newItem = new XElement("GenericEntity", ...); XElement existingItem = findExisting(...); if (existingItem == null) { // -> insert GenericEntitiesNode.Add(newItem); } else { // -> update existingItem.ReplaceWith(newItem); } Document.Save(FileName);

... zum Finden einer Generic Entity:

XElement existingItem = findExisting(...); if (existingItem != null) { GenericEntity genericEntity = new GenericEntity(); genericEntity.ApplicationKey = existingItem.Attribute("ApplicationKey").Value; genericEntity.FullClassName = existingItem.Attribute("FullClassName").Value; genericEntity.EntityId = existingItem.Attribute("EntityId").Value; genericEntity.XmlSerialization = (existingItem.FirstNode as XElement); return genericEntity; } return null;

... und zum Löschen einer Generic Entity:

XElement existingItem = findExisting(...); if (existingItem != null) { existingItem.Remove(); } Document.Save(FileName);

Damit wären wir fertig mit der Implementierung des Generic Entity Xml File Data Storage. Was noch fehlt ist eine Fehlerbehandlung sowie die Implementierung der restlichen Methoden. Diese erfolgt dann analog den gerade beschriebenen Methoden

Nächster Schritt

Im nächsten Teil der Lecture werden wir einen weiteren Data Storage Typ für Generic Entities implementieren. Dieser wird die Daten dann (nicht persistent) im Arbeitsspeicher speichern - ein Generic Entity Memory Data Storage

... to be continued

Autor: Thomas Gysser | www.advadev.de

Mittwoch, 21. August 2013

Generische Datenspeicherung mit XML - Teil 2 (Generic Entity Data Storage)

Bisherige Teile dieser Lecture

Falls Sie die bisherigen Teile dieser Lecture noch nicht kennen:

Kurze Rekapitulation was wir machen wollen

Wir wollen also Objekte in Form ihrer XML-Serialisierung als Strings in einem generischen Data Storage speichern. Dabei kann es sich z.B. um einen SQL Server, eine XML-Datei oder ähnliches handeln. Außerdem haben wir festgestellt, das jedes Objekt über eine eindeutige Objekt-ID identifizierbar sein muss. Fokussieren wir uns auf die rein technische Umsetzung dieser Anforderungen:

Implementierung der technisch notwendigen Elemente

Um der Forderung nach einer eindeutigen Objekt-ID gerecht zu werden, definieren wir zunächst ein Interface IGenericObject. Dieses muss von allen Objekten, welche in unserem Data Storage gespeichert werden können sollen, implementiert werden. Das Interface definiert dabei nur eine String-Property GenericObjectId

Hier ist jetzt allerdings noch ein kleiner Einschub notwendig: Um ein Objekt in unserem Data Storage wirklich eindeutig identifizieren können, reicht die Objekt-ID nicht aus. Wir müssen außerdem noch dessen Klasse kennen (genauer: deren FullClassName). Ein "User" mit der Id "A" ist eben etwas anderes als eine "Address" mit der Id "A". Außerdem wollen wir den gleichen Data Storage evtl. für verschiedene Applikationen benutzen - die vielleicht alle die gleiche User-Klasse verwenden. Aus diesem Grund führen wir noch einen "ApplicationKey" ein

Als erstes zentrales Element definieren wir dann die Klasse GenericEntity. Diese hat als Hauptaufgabe die Durchführung der XML-Serialisierung / XML-Deserialisierung. Sie hat einen Konstruktor, welchem wir das zu speichernde generiche Objekt übergeben können (beim Schreiben in den Data Storage) und eine Methode CreateGenericObject() welche das zu lesende generische Objekt zurück gibt (beim Lesen aus dem Data Storage). Ein Generic Entity ist also die speicherbare Repräsentation eines zu speicherndes Objektes!

Wenig überraschend ist dann noch das zweite zentrale Element - der eigentliche Data Storage, in welchem wir dann die Generic Entities speichern können (wichtig hier - in dem Data Storage werden nicht die Objekte, sondern, wie gesagt, die Generic Entities gespeichert). Mit den konkreten Implementierungen - z.B. ein Generic Entity Data Storage in Form eines SQL-Servers oder einer XML-Datei - beschäftigen wir uns in den folgenden Teilen dieser Lekture. Für den Anfang definieren wir erst einmal ein Interface IGenericEntityDataStorage. Hier legen wir Methoden fest um:

  • eine Generic Entity oder eine Liste von Generic Entities zu speichern
  • eine bestimmte Generic Entity oder eine Liste von Generic Entities zu finden
  • eine bestimmte Generic Entity oder eine Liste von Generic Entities zu löschen
  • zu ermitteln welche Klassenarten und Applikationen gespeichert sind (für administrative Zwecke)

Hier eine Übersicht zu den definieren Elementen:

Die Properties der Klasse GenericEntity sind alles bis auf die XmlSerialization Property vom Datentyp String. Die XmlSerialization Property ist vom Datentyp XElement. Die Umwandlung in einen String erfolgt dann erst innerhalb eines Data Storage (wenn nötig)

... und wie diese miteinander interagieren:

Über den Konstruktor der Klasse GenericEntity wird das zu speichernde "User"-Objekt in einen XML-String serialisiert und die Informationen zur Identifikation werden ermittelt. Das erzeugte Objekt wird im Data Storage gespeichert. Dort kann es wieder ausgelesen werden und über die Methode CreateGenericObject kann das ursprüngliche "User"-Objekt wieder hergestellt werden

Details zur XML-Serialisierung

Der Vollständigkeit halber hier ein Code-Ausschnitt zur XML-Serialisierung eines Objektes in einen String als Beispiel-Implementierung:

User myTestUser = new User();
StringWriter writer = new StringWriter();
XmlWriter xmlWriter = XmlWriter.Create(writer);
XmlSerializer serializer = new XmlSerializer(typeof(User));
serializer.Serialize(xmlWriter, myTestUser);
string xmlString = writer.ToString();

Sowie die Deserialisierung dieses XML-Strings zurück in eine Objekt-Instanz:

string xmlString = "<xml.....";
StringReader reader = new StringReader(xmlString);
XmlReader xmlReader = XmlReader.Create(reader);
XmlSerializer deserializer = new XmlSerializer(typeof(User));
User myDeserializedUser = deserializer.Deserialize(xmlReader) as User;

Anmerkung: der echte Code sieht natürlich etwas anders aus und es werden da dann auch using Directive verwendet!

Nächster Schritt

Nachdem wir nun in der Lage sind ein Objekt mittels XML-Serialisierung prinzipiell speichern zu können, wollen wir mit den konkreten Implementierungen von Generic Entity Data Storages beginnen. Im Einzelnen werden wir dabei in den nächsten Teilen dieser Lecture folgende generische Speicherarten implementieren:

  • In einer XML-Datei mit LINQ to XML
  • Im Arbeitsspeicher (Memory) - hier natürlich ohne Persistenz nach Applikations-Ende
  • In einer SQL Server Datenbank mit LINQ to SQL

... to be continued

Autor: Thomas Gysser | www.advadev.de