Donnerstag, 12. September 2013

Generische Datenspeicherung mit XML - Teil 8 (Zusammenfassung)

Bisherige Teile dieser Lecture

Falls Sie die bisherigen Teile dieser Lecture noch nicht kennen:

Zum Abschluss ...

... wollen wir uns kurz die Konfiguration einer Demo-Anwendung mit einem WCF Web-Service anschauen. Außerdem gibt es noch ein paar Anmerkungen zum Thema "Performance"

Demo WCF Web-Service

Hier finden Sie eine kleine Demo-Anwendung. Diese besteht aus zwei Teilen - zum einen eine ASP.Net Web-Anwendung mit dem Data Storage Service als Web Service. Zum anderen aus einer kleinen WPF- Anwendung, welche diesen Service dann als Data Storage nutzt. Sie können den Code hier herunterladen: Generic Data Storage Demo

Entpacken Sie die Datei und starten Sie die Solution. Legen Sie dann zuerst das Projekt "GenericDataStorageDemo.Web" als Startprojekt fest und rufen Sie den Web-Service einmal im Browser auf über
http://localhost:53251/MemoryDataStorageService.svc
damit er läuft. Anschließen legen Sie das WPF-Projekt als Startprojekt fest und starten es

Performance

Um ein Gefühl dafür zu bekommen, was mit dieser Komponente möglich ist hier ein paar ganz grobe Kennzahlen zur Performance. Um 100 "User"-Objekte zu speichern werden folgende Zeiten ca. benötigt:

  • Memory Data Storage: ca. 150 ms
  • SQL Server Data Storage: ca. 1750 ms
  • XML File Data Storage: ca. 2000 ms

Für etwas größere Applikationen bietet es sich an, analog zu dem XML-File Data Storage einen XML-Directory Data Storage zu implementieren, welcher jeden Objekt-Typ für jede Applikation einer extra Datei speichert. Dieser XmlDirectoryDataStorage ist Bestandteil der Komponente (allerdings werden wir ihn nicht weiter vorstellen, da die Implementierung lediglich eine Erweiterung der bisherigen Implementierungen darstellt)

Lizenzbedingungen

Die hier vorgestellte Software-Komponente kann entsprechend der Lizenzbedingungen frei eingesetzt werden. Den Quellcode und die zugehörigen Lizenzbedingungen finden Sie unter CodePlex

the end!

Damit sind wir am Ende dieser Lecture angelangt. Für Fragen und Anmerkungen können Sie mich gerne über die angegebenen Kontaktdaten kontaktieren!

Autor: Thomas Gysser | www.advadev.de

Mittwoch, 11. September 2013

Generische Datenspeicherung mit XML - Teil 7 (Data Storage als Web-Service mit WCF)

Bisherige Teile dieser Lecture

Falls Sie die bisherigen Teile dieser Lecture noch nicht kennen:

Kurze Rekapitulation was wir machen wollen

Nachdem wir alle wesentlichen Komponenten für einen generischen Data Storage implementiert haben, wollen wir diesen jetzt auch noch als WCF-Service verfügbar machen (vorausgesetzt wird ein Verständnis für die grundlegende Funktionsweise von WCF - dies zu vermitteln ist nicht Bestandteil dieser Lecture)

Übersicht zur geplanten Implementierung

Für den Verwender der Komponente sollte solch ein Service Data Storage genau gleich sein wie die bisher schon vorgestellten Data Storages. Von dem her ist auch die Implementierung analog zu diesen. Wir benötigen einen Service Data Storage, welcher intern einen Generic Entity Service Data Storage nutzt, welcher wiederrum die Daten mit einem Web-Service austauscht. Dieser speichert die Daten dann in einem der bekannten Generic Entity Data Storages:

Für die Implementierung mit WCF müssen wir für den Service einen ServiceContract in Form eines Interfaces implementieren - das IDataStorageService Interface. Außerdem müssen die zu übertragenden Daten einen DataContract implementieren. Hier müssen wir allerdings nur die Klasse GenericEntity entsprechend erweitern. Aus diesem Grund übertragen wir übrigens auch mit dem Service nicht die eigentlich zu speichernden Objekte sondern serialisieren diese bereits clientseitig - sonst müssten bereits die zu speichernden Objekte den DataContract definieren

Hier ein Überblick zu den benötigten Klassen der Client-Seite:

... und der Server-Seite:

Implementierung

Beginnen wir auf der Client-Seite mit der eigentlichen Implementierung. Die Klasse ServiceDataStorage ist dabei trivial - wie alle anderen Data Storages übergibt sie ihrer Basisklasse lediglich einen Generic Entity Data Storage

Interessanter ist die Implementierug der Klasse GenericEntityServiceDataStorage. Allerdings ist auch diese nicht allzu aufwendig. Im Konstruktor erzeugen wir uns über eine "Channel-Factory" ein Objekt vom Typ IDataStorageService. Die eigentliche Implementierung der Data Storage-Methoden sind dann nur noch Wrapper-Aufrufe mit diesem Objekt. Die "Channel-Factory" bekommt die Informationen über den anzusprechenden Service in Form eines "Endpoint Configuration Names" übergeben. Hier das wesentliche Code-Segment aus dem Konstruktor:

service = new ChannelFactory<IDataStorageService>(endpointConfigName); service.Credentials.UserName.UserName = serviceUserName; service.Credentials.UserName.Password = servicePassword; DataStorageService = service.CreateChannel();

Eine kleine Ergänzung implementieren wir allerdings noch - der Zugriff auf die Methode "FindAppicationKeys" wollen wir nicht so einfach machen, wenn wir den Data Storage als Service veröffentlichen. Welche anderen Application Keys sonst noch im Data Storage verwendet werden geht den einfachen Verwender dieses Services nichts an. Aus diesem Grund führen wir hier Administrator-Credentials ein welche beim Aufruf dieser Methoden vorliegen müssen

Auch auf der Server-Seite ist die Implementierung trivial - der ServiceDataStorage dient lediglich als Wrapper für den eigentlichen Generic Entity Data Storage auf dem Server

Damit sind wir fertig mit der reinen Erweiterung unserer Komponente. Von Interesse wäre jetzt noch eine Beispiel-Konfiguration für solch ein Service-Szenario. Dieses schauen wir uns dann im nächsten Teil dieser Lekture an

Nächster Schritt

Damit sind wir eigentlich am Ende dessen, was mit dieser Lecture erreicht werden sollte. Wir haben eine Komponente entwickelt, mit Hilfe derer wir einfach und auf generische Art und Weise Objekte speichern können und wir können diese nun auch als WCF-Service veröffentlichen. Im abschließenden letzten Teil der Lecture stellen wir nun noch die Konfiguration einer Demo-Anwendung vor

... to be continued

Autor: Thomas Gysser | www.advadev.de

Montag, 9. September 2013

Generische Datenspeicherung mit XML - Teil 6 (Data Storage)

Bisherige Teile dieser Lecture

Falls Sie die bisherigen Teile dieser Lecture noch nicht kennen:

Kurze Rekapitulation was wir machen wollen

Auf unserem Weg zu einer Komponente, welche es uns ermöglicht Daten einfach in Form ihrer XML-Serialisierung zu speichern haben wir bisher Folgendes erreicht: Wir können Objekte (welche das Interface IGenericObject implementieren) in Generic Entities konvertieren (und zurück). Diese können wir dann in einer SQL-Datenbank, in einem XML-File oder im Arbeitsspeicher speichern. Jetzt müssen wir die bisher implementierten Funktionen den Benutzern der Komponente nur noch einfach verfügbar machen

Übersicht zur geplanten Implementierung

Wie schon im ersten Teil erwähnt, ist die Idee dabei das Ganze mit Hilfe von Generics zu lösen. Hier nochmal der Code-Ausschnitt, wie er am Ende beim Verwender der Komponente aussehen sollte:

MyDataStorage.Save<User>(newUser) User existingUser = MyDataStorage.Find<User>("myUserName")

Hierzu definieren wir zunächst einmal ein Interface IDataStorage welches uns erlaubt Objekte typsicher zu speichern, zu lesen oder zu löschen (im oberen Code wäre das Objekt "MyDataStorage" ein IDataStorage):

Der Data Storage bekommt einen "ApplicationKey" zugewiesen - er ist der Speicher für genau eine Applikation. Damit müssen wir diese Angabe nur einmal machen und müssen nicht bei jedem einzelnen Zugriff (z.B. "Save(user)") einen Key übergeben

Anschließend müssen wir dann wieder konkrete Data Storages implementieren - für einen SQL Server, eine XML-Datei, ... Allerdings sind alle diese Implementierungen genau gleich: Sie verwenden intern den entsprechenden GenericEntityDataStorage. Des wegen erzeugen wir auch eine abstrakte Basisklasse DataStorageBase in welcher wir die ganze Implementierung einmal machen. Von dieser Klasse leiten wir dann die einzelnen Data Storages ab. Alles was diese Klassen dann noch machen ist, der Basisklasse den zu verwendenden Generic Entity Data Storage zu übergeben. Hier eine Übersicht:

Nochmal zum besseren Verständnis: In einem Generic Entity Data Storage speichern wir Generic Entities, in einem Data Storage speichern wir beliebige Objekte (welche das Interface IGenericObject implementiert haben). Ein Data Storage wandelt die zu speichernden Objekte in Generic Entities um und speichert diese dann in dem ihm zugehörenden Generic Entity Data Storage

Implementierung der Klasse DataStorageBase

Konstruktor

Der Konstruktor bekommt einen Generic Entity Data Storage sowie ein Application Key übergeben. Diese Objekte werden als private Klassen-Properties gespeichert

Speichern, Finden und Löschen

Beim den eigentlichen Hauptoperationen des Data Storage - dem Speichern, Finden und Löschen - greifen wir hauptsächlich auf den Generic Entity Data Storage zu

Hier die wesentlichen Code-Segmente zum Speichern:

GenericEntity genericEntity = new GenericEntity(genericObject, ApplicationKey); GenericEntityDataStorage.Save(genericEntity);

... zum Finden:

GenericEntity genericEntity = GenericEntityDataStorage.Find(id, ApplicationKey, typeof(T).FullName); if (genericEntity != null) { return (T)genericEntity.CreateGenericObject(typeof(T)); } return default(T);

... und zum Löschen:

GenericEntityDataStorage.Delete(new GenericEntity(genericObject, ApplicationKey));

Die Implementierungen der Kassen XmlFileDataStorage, MemoryDataStorage und SqlServerDataStorage sind einfach. Der XmlFileDataStorage bekommt im Konstruktor einen Dateinamen übergeben - hieraus wird ein GenericEntityXmlFileDataStorage erzeugt, welcher dem Konstruktor der Basisklasse übergeben wird. Bei dem MemoryDataStorage muss nichts übergeben werden, bei dem SqlServerDataStorage ein Connection-String

Nächster Schritt

Damit haben wir unser Hauptziel erreicht - wir haben nun eine einfache Möglichkeit, um Objekte in einer Applikation persistent zu speichern. Hauptsächlich bei der Verwendung einer Sql-Server Datenbank ist der direkte Zugriff aus einer Applikation heraus allerdings nicht der optimale Weg. Aus diesem Grund wollen wir um diese Komponente abzuschließen noch einen Web-Service implementieren, welcher den Zugriff auf den eigentlichen Data Storage kapselt. Implementieren werden wir diesen mit Hilfe der .Net Windows Communication Foundation (WCF)-Technologie

... to be continued

Autor: Thomas Gysser | www.advadev.de

Montag, 2. September 2013

Generische Datenspeicherung mit XML - Teil 5 (SQL Server 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 dritten 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 SQL Server Datenbank

Übersicht zur geplanten Implementierung

Für die Implementierung des Data Storage benötigen wir genau eine Klasse: den GenericEntitySqlServerDataStorage.
Diese muss dann das Interface IGenericEntityDataStorage implementieren. Die einzige Information, welche Sie benötigt, ist welche Datenbank zu verwenden ist und wie auf diese zugegriffen werden kann. Da diese Angaben wesentlich sind für ihr ordnungsgemäßes Funktionieren, werden wir sie in Form eines Connection-Strings bereits im Konstruktor als Argument vorsehen

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

Wichtige Frage im Zusammenhang mit der Datenbank - wie soll diese aufgebaut 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 eine Datenbank mit genau einer Tabelle erstellen. Diese wird dann die entsprechenden Eigenschaften der Generic Entities als Spalten enthalten. Der Tabelle werden wir den Namen "tGenericEntities" geben. Hier das genaue Tabellen-Design:

Eigentlich ist jeder Eintrag durch die Kombination der 3 String-Eigenschaften eindeutig identifiziert. Um uns das Leben einfacher zu machen und das ganze etwas performanter zu gestalten legen wir als Key in der Datenbank aber eine künstliche Id an, welche wir dann als Primary Key verwenden. Diese wird auf "Auto-Inkrement" gesetzt und zählt selbständig hoch. Irgendwo extern verwendet wird sie allerdings nicht

Datenbank-Zugriff mit LINQ to SQL

Um auf die Datenbank mittels LINQ to SQL zuzugreifen benötigen wir zuerst ein DataContext-Objekt für unsere Datenbank, welches wir dann für die zu implementierenden CRUD-Operationen verwenden (CRUD = "Create, Read, Update, Delete"). Das geht mit Visual Studio sehr einfach. Wir fügen dem Projekt ein neues Element vom Typ "LINQ to SQL Klassen" hinzu, welches wir "GenericEntities.dbml" nennen. Im Server-Explorer öffnen wir dann die Datenbank und ziehen die Tabelle "tGenericEntities" aus dem Server-Explorer in die Design-Ansicht der neu erzeugten Datei "GenericEntities.dbml". Visual Studio erzeugt uns dabei zwei neue Klassen: tGenericEntity und GenericEntitiesDataContext

Da der Name und die Property-Namen der erzeugten Klasse tGenericEntity nicht ganz zu unseren Code-Konventionen passt benennen wir sie im Designer um, so dass wir anschließend folgende beiden neuen Klassen haben:

Die Klasse GenericEntitiesDataContext bekommt im Konstruktor einen Connection-String für die Datenbank-Verbindung übergeben und stellt uns dann über die Property GenericDatabaseEntities Methoden zur Verfügung um mit Objekten des Typs GenericDatabaseEntity umgehen zu können. Da diese den von uns eigentlich zu speichernden Objekten des Typs GenericEntity genau entsprechen lassen sie sich einfach ineinander umwandeln und wir können mit der eigentlichen Implementierung beginnen:

Implementierung mit LINQ to SQL

Kommen wir nun zur Implementierung der Klasse GenericEntitySqlServerDataStorage:

Konstruktor

Als erstes betrachten wir den Konstruktor. In diesem instanziieren wir ein Objekt der gerade automatisch neu erzeugten Klasse GenericObjectsDataContext mittels des übergebenen Connection-Strings

Hier die wesentlichen Code-Segmente des Konstruktors:

DataContext = new GenericEntitiesDataContext(connectionString);

Hilfsmethoden

Im nächsten Schritt implementieren wir dann eine Hilfsmethode um ein Generic Entity mithilfe seiner ID-Eigenschaften in der Datenbank zu finden

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

return DataContext.GenericDatabaseEntities.SingleOrDefault<GenericDatabaseEntity>( c => c.ApplicationKey == applicationKey && c.FullClassName == fullClassName && c.EntityId == entityId );

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 DataContext-Objektes um die Operationen durchzuführen. Anschließend machen wir die Änderungen mittels der SubmitChanges-Methode des DataContext-Objektes persistent

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

GenericDatabaseEntity existingItem = findExisting(...); if (existingItem == null) { // -> insert GenericDatabaseEntity newItem = new GenericDatabaseEntity(); newItem.ApplicationKey = genericEntity.ApplicationKey; newItem.FullClassName = genericEntity.FullClassName; newItem.EntityId = genericEntity.EntityId; newItem.XmlSerialization = genericEntity.XmlSerialization; DataContext.GenericDatabaseEntities.InsertOnSubmit(newItem); } else { // -> update existingItem.XmlSerialization = genericEntity.XmlSerialization; } DataContext.SubmitChanges();

... zum Finden einer Generic Entity:

GenericDatabaseEntity existingItem = findExisting(...); if (existingItem != null) { GenericEntity genericEntity = new GenericEntity(); genericEntity.ApplicationKey = existingItem.ApplicationKey; genericEntity.FullClassName = existingItem.FullClassName; genericEntity.EntityId = existingItem.EntityId; genericEntity.XmlSerialization = existingItem.XmlSerialization; return genericEntity; } return null;

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

GenericDatabaseEntity existingItem = findExisting(...); if (existingItem != null) { DataContext.GenericDatabaseEntities.DeleteOnSubmit(existingItem); DataContext.SubmitChanges(); }

Damit wären wir fertig mit der Implementierung des Generic Entity Sql Server 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 uns genauer überlegen, wie wir die nun implementierte Funktionalität einem Verwender der Komponente am einfachsten zur Verfügung stellen können. Mit der rein technischen Implementierung der Datenspeicherung sind wir an dieser Stelle erst mal fertig

... to be continued

Autor: Thomas Gysser | www.advadev.de

Dienstag, 27. August 2013

Generische Datenspeicherung mit XML - Teil 4 (Arbeitsspeicher 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 weiteren 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 den Arbeitsspeicher. Da dieser selbstverständlich nicht persistent ist, stellt sich hier natürlich die Frage - warum einen nicht-persistenten Speicherort wählen? Die Idee ist hier, dass wir für kleine Demo-Anwendungen einen Data Storage haben, für welchen wir sonst nichts einrichten müssen - keinen SQL Server, keine Dateizugriffe, ... Und nach dem Ende des Demos sind die Daten automatisch wieder weg! Und wenn wir aus dem Demo eine richtige Applikation machen wollen müssen wir nur den Data Storage austauschen

Übersicht zur geplanten Implementierung

Für die Implementierung des Data Storage benötigen wir genau eine Klasse: den GenericEntityMemoryDataStorage.
Diese muss dann das Interface IGenericEntityDataStorage implementieren. Hier jetzt erst einmal die Definition der Klasse:

Die Klasse selbst speichert die Daten dann in einem mehrstufigen Dictionary. Im Folgenden die Deklaration und wie sie gemeint ist:

Dictionary<string, Dictionary<string, Dictionary<string, GenericEntity>>> myDataStorage["ApplicationKey"]["FullClassName"]["EntityId"] = myGenericEntity;

Hier jetzt aber noch eine wichtige Ergänzung: wir dürfen in dem gerade vorgestellten Dictionary nicht direkt die zu speichernden Generic Entities als Referenz speichern! Wenn wir Objekte aus einem SQL Server oder aus einer XML- Datei wieder auslesen bekommen wir ja auch nicht die Orginal-Objekte wieder zurück. Aus diesem Grund werden auch nicht die Generic Entities als Referenz gespeichert sondern vielmehr geklonte Instancen davon

Implementierung

Hilfsmethoden

Im nächsten Schritt implementieren wir dann eine Hilfsmethode um ein Generic Entity mithilfe seiner ID-Eigenschaften im Dictionary zu finden sowie eine Hilfsmethode um ein Generic Entity zu klonen. Der Code um ein Generic Entity zu finden ist trivial. Hier suchen wir mit dem "ApplikationKey" als Key, im Ergebnis dann mit dem "FullClassName" als Key und in dessen Ergebnis mit der "EntityId" als Schlüssel. Interessanter ist das Klonen der Objekt-Instanzen

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

StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); XmlWriter xmlWriter = XmlWriter.Create(writer); XmlSerializer serializer = new XmlSerializer(typeof(GenericEntity)); serializer.Serialize(xmlWriter, item); string xml = writer.ToString(); StringReader reader = new StringReader(xml); XmlReader xmlReader = XmlReader.Create(reader); return serializer.Deserialize(xmlReader) as GenericEntity;

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 Hilfsmethoden zu. Auch hier ist die eigentliche Implementierung trivial - es handelt sich nur um Operationen auf das Dictionary

Damit wären wir fertig mit der Implementierung des Generic Entity Memory 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 mittels LINQ to SQL in einer SQL-Datenbank speichern - ein Generic Entity Sql Server Data Storage

... to be continued

Autor: Thomas Gysser | www.advadev.de

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

Dienstag, 20. August 2013

Generische Datenspeicherung mit XML - Teil 1 (Einleitung)

Problemstellung

Kennen Sie das Problem - Sie entwickeln eine kleine Applikation oder wollen nur mal schnell etwas ausprobieren und müssen in diesem Zusammenhang Daten persistent speichern (und es ist dabei egal wo und wie)? Dafür immer wieder eine Datenzugriffsschicht für eine Datenbank oder eine Datendatei neu implementieren zu müssen kostet oft einen unnötig hohen Zeitaufwand. Stellt sich die Frage "geht das nicht generisch?"

Genau mit dieser Frage werden wir uns in dieser Lecture beschäftigen ... und ich kann Ihnen jetzt schon verraten - ja, es geht! :-)

Aber Achtung: Die Zielsetzung dabei ist „einfach mal schnell“ Daten speichern zu können. Die Zielsetzung ist NICHT die hier vorgestellte Komponente in komplexeren Applikation mit hohen Performance-Ansprüchen, großer Flexibilität oder mit komplexen Datenstrukturen einzusetzen!

Lösungsansatz

Die prinzipielle Idee ist, die zu speichernden Daten in Form ihrer XML-Serialisierung als String zu speichern. Daraus folgend müssen wir also nur einmal eine generische Implementierung entwickeln, wie man einen String in einer Datenbank, in einer Datei bzw. in sonst einem Speicherort speichert - sprich: wie man also einen String in einem Data Storage speichert

Damit ist die erste wichtige Voraussetzung schon gegeben, für welche Art von Objekten diese generische Datenspeicherung verwendet werden kann: nur für Objekte, welche XML-Serialisierbar sind! Für die in unserem Fokus stehenden "einfachen" Klassen (wie z.B. eine User- oder Adress- Klasse) ist das allerdings keine relevante Einschränkung

Wichtiger ist eine andere Voraussetzung - wann immer wir Daten speichern und wieder lesen wollen, müssen wir diese auch identifizieren können. Jedes Objekt, welches in unserem Data Storage gespeichert werden soll, muss daher eine eindeutige ID haben

Ziel dieser Lecture

Ziel dieser Lecture ist es, Schritt für Schritt zu beschreiben, wie wir eine Komponente entwickeln, mit welcher die oben beschriebene generische Datenspeicherung einfach in einer Applikation integrierbar ist

Schritt für Schritt zu einer Komponente

Bevor wir uns mit Fragen der Realisierung auseinandersetzen, empfiehlt es sich im ersten Schritt kurz darüber nachzudenken, wie wir eine zu entwickelnde Komponente gerne selbst als Verwender einsetzen würden. Hier ein kurzes Stück Pseudo-Code, wie ich als User gerne mit der Komponente umgehen würde:

MyDataStorage.Save<User>(newUser) User existingUser = MyDataStorage.Find<User>("myUserName")

Der Data Storage sollte typsicher mit den zu speichernden Klassen umgehen können. Aus diesem Grund sehen wir die Verwendung von Generics vor

Als erstes werden wir uns jetzt allerdings mit den technisch notwendigen Fragestellungen auseinander setzen: Wir müssen, um die Lösungsidee umsetzen zu können, einen Weg finden, die XML-Serialisierungen von Objekten als Strings in einem Data Storage speichern zu können. Und dieser muss in verschiedenen Ausprägungen implementiert werden - z.B. mittels eines SQL Servers oder einer XML-Datei

Anschließend überlegen wir uns genauer, wie ein Verwender der Komponente deren Funktionalität am einfachsten in seiner Applikation nutzen kann. Statt der technischen Sichtweise fokussieren wir uns auf das Thema Usability

Im letzten Schritt soll es dann noch ermöglicht werden einen Data Storage einfach per WCF als Service zu veröffentlichen

... to be continued

Autor: Thomas Gysser | www.advadev.de