Fallbeispiele 7 Fallbeispiele 275 Fallbeispiele 7 Fallbeispiele Um einen Überblick über die Notwendigkeit der Erzeugung von XML-Daten aus relationalen Daten und ihre Umwandlung in wiederum relationale Daten zu gewinnen, folgt ein kurzes Beispiel, das die allgemeine Architektur einer Import-/ Export-Schnittstelle und eine (De)Serialisierungsschnittstelle auf Basis von T-SQL beschreibt. Solche Import-/Export-Schnittstellen können notwendig sein, weil andere Systeme bestimmte Datenausschnitte der exportierenden Datenbank benötigen, oder weil die eigene Datenbank als Ziel für solche Daten vorhanden ist. Dies können genauso Buchungs- und Reservierungssysteme wie Untersuchungs- und Analysesysteme, die auf Basis mehrerer Datenbanken, die möglicherweise sogar verschiedene Hersteller zum Hintergrund haben, erstellt werden, sein. Techniken der Serialisierung und Deserialisierung können dann interessant sein, wenn per XML die Serialisierung durchgeführt werden soll und die Daten nicht in Form einer reinen XML-Datei gespeichert werden soll, sondern stattdessen die Daten relational zerlegt und in einer Datenbank gespeichert werden sollen. Dies ist auch dann eine interessante Lösung, wenn bspw. eigene, auf XML-basierte Dateiformate benötigt werden. In diesem Fall würde die Serialisierung sowohl für die Speicherung im Dateisystem mit einer spektakulären Endung (und natürlich nicht .xml) als auch für die Speicherung in der Datenbank zum Einsatz kommen. 7.1 De-/Serialisierung von Objekten Serialisierung bedeutet, dass der Zustand eines Objekts in eine Zeichenkette zerlegt wird. Dies ist im Normalfall eine in jeder objektorientierten Sprache bereits vorhandene Möglichkeit. Es entstehen dabei automatisch generierte Zeichenketten, die durchaus kein XML-Format besitzen (müssen). Im Normalfall kann man allerdings durchaus diese Zeichenkette lesen und verstehen, da in irgendeiner Art und Weise eine SchlüsselWerte-Liste abgebildet wird und die Namen der Felder/Eigenschaften mit ihren zugehörigen Werten in dieser langen Zeichenkette erscheinen. Um Collections, die wiederum Objekte enthalten können, oder Arrays ranken sich dann bspw. noch zusätzliche Zeichen wie eckige oder geschweifte Klammern, um die gesamten Daten sowie die einzelnen Einträge zu unterscheiden. Es besteht darüber hinaus auch die Möglichkeit, den Objektzustand in einer Datenbank zu speichern. 276 Fallbeispiele 7.1.1 XML-Serialisierung als relationale Zerlegung Neben der automatischen Serialisierung in eine beliebige, jedoch von der Programmiersprache vorgegebene Zeichenkette gibt es auch die Möglichkeit, eine eigene Serialisierung vorzunehmen. Dies ist je nach Programmiersprache einfacher oder schwieriger. Mit .NET ist es besonders einfach; sofern man dann auch noch die XMLStandardserialisierung verwendet, ist es genauso einfach wie eine Zerlegung in einer einfachen Programmiersprache wie PHP. Es besteht allerdings immer auch die Möglichkeit, eine eigene Serialisierung zu schreiben, was vom Schwierigkeitsgrad wiederum ein wenig von der Programmiersprache abhängt. Für die Überlegung dieses Abschnitts genügt es, dass eine solche Möglichkeit besteht und dass darüber hinaus die Daten in eine relationale Datenbank und/oder eine Textdatei gespeichert werden sollen. Durch die Serialisierung ist es möglich, den Objektzustand dauerhaft zu speichern. Dies erlaubt es, ein Objekt in eben diesem Zustand zu einem späteren Zeitpunkt wieder zu aktivieren. In Software, die keine Datenbank benötigt, könnte dies bspw. ein Spielzustand (Armeeaufbau, Kampfsituation, erreichte Punktestand, Fähigkeiten) sein. Kommt eine Datenbank ins Spiel, dann handelt es sich möglicherweise um die Situation, dass die Daten für die Verwendung durch andere Benutzer (Projektverwaltung) dauerhaft gespeichert und für relationale Abfragen zur Verfügung stehen wollen. Eine solche Serialisierung in XML ermöglicht es darüber hinaus auch noch, sehr einfach ein eigenes Dateiformat zu erstellen. Die in XML-serialisierten Objektzustände stellen dann das Dateiformat dar, welches im Dateisystem gespeichert und nachher wieder durch die Software eingelesen werden kann. Die erste Abbildung stellt die Architektur einer solchen Serialisierungsschnittstelle zunächst grob, die zweite Abbildung dagegen etwas genauer dar. In der ersten Abbildung sieht man unten zunächst die Datenbank, in der Mitte die Schnittstelle, welche in XML-Format die Daten erwartet, zerlegt und in der Datenbank speichert und dann später in XML zusammensetzt und zurückliefert. In vielfältiger Weise ist dies überhaupt das Grundprinzip einer über XML realisierten Import-/ Exportschnittstelle, wobei allerdings in diesem besonderen Fall die Datenübertragung für den besonderen Prozess von Serialisierung und Deserialisierung zum Einsatz kommt. Die Abbildung zeigt dann im oberen Bereich auf der linken Seite die Software, in der die übermittelten Daten wieder in Objekte umgewandelt werden oder welche die Objekte serialisiert und speichert. Auf der rechten Seite der Abbildung ist eine XML-Datei angegeben, die mit einer eigenen Endung das Dateiformat der Anwendung zeigt. In dieser Datei befindet sich nichts anderes als die XML-Darstellung, welche von der Schnittstelle erzeugt bzw. erwartet wird. 277 Fallbeispiele Die Schnittstelle selbst sammelt aus verschiedenen Tabellen, in denen die Daten relational zerlegt sind, die benötigten Datensätze und stellt sie in einem selbst definierten XML-Format zusammen. Hier kann man sich zusätzlich vorstellen, wie Parameter zu Filterzwecken genutzt werden, um bestimmte Objektzustände nach Parametern auszuwählen. Dies ist dann auch das Format, welches von der Schnittstelle für den Import, d.h. für den Speichervorgang erwartet wird. In diesem Fall sendet die Software eine XML-Darstellung an die Schnittstelle, deren Aufgabe darin besteht, diese Darstellung relational zu zerlegen. Software .xml XMLSchnittstelle XMLSchnittstelle Datenbank Tab 1 Tab 2 Tab 2 Bereich 1 ... Tab 1 Tab 2 Tab 3 ... Bereich 2 Abbildung 4.1: Prinzip der Serialisierung Die zweite Abbildung greift die erste noch einmal auf, variiert sie allerdings ein wenig, indem sie etwas mehr Details zeigt. Dieses Mal ist wiederum auf der linken unteren Seite die Datenbank angezeigt, welche sowohl die Tabellen als auch die Schnittstelle enthält. Darüber ist die Software angegeben, die nun offensichtlich aus mehreren Objekten besteht. Diese Objekte können einzeln oder zusammengesetzt in der Datenbank bzw. natürlich auch im Dateisystem gespeichert werden. In diesem Fall muss es einen 278 Fallbeispiele Serialisierer geben, der in der Lage ist, die verschiedenen Eigenschaftswerte der Objekte in eine XML-Darstellung zu zerlegen und der Schnittstelle zu übergeben. Unten sieht man wiederum die verschiedenen Tabellen, welche die relationale Darstellung der übermittelten Objektdaten repräsentieren. Sofern es mehrere Schnittstellen gibt, welche der Reihe nach mit Teil-XML-Darstellungen aufgerufen werden, würde dieser Tabellenverbund dann auch einen sachlogischen Sinnzusammenhang abbilden. Objekt Objekt Objekt Objekt Objekt Objekt Software Prozedur Funktion Sicht Prozedur Funktion Sicht Datenbank Tabelle 1 Tabelle2 Tabelle 3 ... Tabelle 1 Tabelle 2 Tabelle 3 ... Abbildung 4.2: Technik der Serialisierung Als mögliche Lösungen für die konkrete Implementierung einer solchen Schnittstelle stehen für den Export Prozeduren, Funktionen und Sichten, für den Import dagegen hauptsächlich Funktionen und Prozeduren zur Verfügung. Während eine Funktion rechtsseitig einer Zuweisung aufgerufen werden kann und ihre XML-Daten daher in Form des Rückgabewerts zurückliefert, wird normalerweise eine Prozedur verwendet, welche über einen Ausgabeparameter die benötigten Daten der Anwendung zur Verfü- 279 Fallbeispiele gung stellt. Eine Prozedur ist auch geeignet, XML-Daten entgegenzunehmen und sie dann passend zu zerlegen. Eine Funktion bietet zwar auch eine analoge Möglichkeit an, Parameter zu empfangen, sie muss allerdings auch einen Rückgabewert liefern, der möglicherweise gar nicht benötigt wird. Eine Sicht eignet sich insbesondere für die Zusammenstellung der Daten im Bereich des Exports. 7.1.2 Beispiele Die Möglichkeiten von Serialisierung und Deserialisierung sollen aufgrund einer abgewandelten und vereinfachten Product2-Tabelle veranschaulicht werden. Dabei verwendet man auf der einen Seite eine Prozedur, die ein XML-Resultset zurückliefert, und auf der anderen Seite eine SELECT…FOR XML-Abfrage, welche die XML-Daten direkt aus der Datenbank abruft. Neben diesen beiden Lösungsansätzen für die Deserialisierung zeigt das Beispiel noch, wie das Zurückschreiben von Datensätzen durch Serialisierung funktioniert, sodass aus einem Objekt in der Software zunächst XML wird, welches eine Prozedur umwandelt, um das Objekt schließlich wieder in einer relationaler Tabelle zu speichern. Bei der Vorstellung der Deserialisierung zeigt das Beispiel auch noch, wie man die Standarddeserialisierung und das DOM einsetzen kann, um die XML-Daten aus der Datenbank abzurufen bzw. im Klienten weiterzuverarbeiten. Eine letzte Möglichkeit besteht nun noch darin, einen Webdienst zu verwenden. Dieser liefert dann als Antwort eine XML-Struktur in Form einer SOAP-Nachricht mit dem serialisierten Objekt, während die Anfrage des Klienten den Serialisierungsvorgang abbildet. Dies entspricht allerdings im Wesentlichen den Techniken, die schon vorgestellt wurden, als die Verwendung von komplexen Nachrichten eingeführt wurde, sodass dies nicht noch einmal aufgegriffen wird. 7.1.2.1 Vorbereitung im MS SQL Server Auf dem Server sind einige Vorbereitungen zu treffen, um ein funktionstüchtiges Beispiel erstellen zu können. Zunächst benötigt man eine Datenstruktur, die im einfachsten Fall aus einer einzigen Tabelle besteht. Es ist auch möglich, eine verschachtelte Struktur aus einer Eltern-Kind-Tabellenverknüpfung zu erstellen, die dann sowohl in der Software als auch nachher beim Speichervorgang wieder zerlegt wird. Es ließe sich darüber hinaus auch noch eine XML-Spalte vorstellen, die komplexe Inhalte enthält, welche man weder in der Daten- noch in der Anwendungsschicht zerlegen möchte. Im aktuellen Beispiel handelt es sich um eine sehr vereinfachte Darstellung der Product-Tabelle mit nur fünf Spalten. Diese haben drei verschiedene Datentypen, sodass man sich auf der Klientenseite dem Problem der Typumwandlung gegenüber sieht. 280 Fallbeispiele CREATE TABLE Production.Product2 ( ProductID int IDENTITY(1,1) NOT NULL PRIMARY KEY, Name varchar(30) NOT NULL, ProductNumber nvarchar(25) NOT NULL, ListPrice money NOT NULL, StandardCost money NOT NULL) 712_01.sql: Tabelle für Beispieldaten Die Daten stammen direkt aus der gewöhnlichen Product-Tabelle, die man mit Hilfe einer INSERT…SELECT-Anweisung überträgt. INSERT INTO Production.Product2 (Name, ProductNumber, ListPrice, StandardCost) SELECT TOP 5 Name, ProductNumber, ListPrice, StandardCost FROM Production.Product WHERE ListPrice > 0 712_01.sql: Übernahme von Daten Wie oben schon erwähnt, kann man sich verschiedene Objekte denken, die aus einer relationalen Struktur XML-Daten nach außen liefern. Im Rahmen der Diskussion einer Import-/Exportschnittstelle, die das Thema des folgenden Abschnitts bildet, kann es hier Funktionen, Prozeduren und auch XML-Sichten geben. Die verschiedenen Objekte erfordern zwar unterschiedlichen Quelltext, doch das Grundprinzip ist natürlich dasselbe, sodass es an dieser Stelle genügen soll, nur eine Prozedur zu verwenden. Diese liefert auch die XML-Daten nicht als Parameter zurück, sondern kapselt nur eine Abfrage, welche XML-Daten zurückliefert. CREATE PROCEDURE Production.GetProduct( @vProductNumber nvarchar(25)) AS BEGIN SELECT * FROM Production.Product2 AS Product WHERE ProductNumber = @vProductNumber FOR XML AUTO, ELEMENTS END 712_01.sql: Prozedur mit XML-Rückgabe 281 Fallbeispiele Um wiederum XML-Daten entgegen zu nehmen, lassen sich Prozeduren oder Funktionen betrachten. Sichten dürften wohl in diesem Bereich eher selten anzutreffen sein, da die XML-Daten ja nicht direkt gespeichert werden sollen, sondern erst noch zerlegt werden müssen. Ob hier dann eine Sicht in Kombination mit einem Trigger, der die Datenzerlegung durchführt, geeignet ist, ist schlecht vorstellbar, weil eine Reihe von Funktionen und Prozeduren vielmehr die Möglichkeit bieten, eine Anwendungsschnittstelle anzubieten. Die Prozedur, die im nachfolgenden Quelltext präsentiert wird, erwartet für die Aktualisierung von Produktdaten also nun einen nvarchar(max)-Parameter, der mit Hilfe von openxml() relational zerlegt und dann für die Aktualisierung verwendet wird. Eine Einfüge-Operation wäre mit diesen relationalen Daten ebenfalls über Variable oder direkt über eine INSERT…SELECT-Anweisung denkbar. CREATE PROCEDURE Production.UpdateProduct( @vProductXML nvarchar(max)) AS BEGIN -- Variablen DECLARE @vProductID int, @vName nvarchar(30), @vProductNumber nvarchar(25), @vListPrice money, @vStandardCost money, @idoc int -- Standardzerlegung und gleichzeitige Eintragung EXEC sp_xml_preparedocument @idoc OUTPUT, @vProductXML SELECT @vProductID @vName = ProductID, = Name, @vProductNumber = ProductNumber, @vListPrice = ListPrice, @vStandardCost = StandardCost FROM OPENXML(@idoc, '/Product',2) WITH (ProductID Name int, varchar(50), ProductNumber varchar(25), ListPrice 282 money, Fallbeispiele StandardCost money) -- Aktualisierung UPDATE Production.Product2 SET Name = @vName, ProductNumber = @vProductNumber, ListPrice = @vListPrice, StandardCost = @vStandardCost WHERE ProductID = @vProductID END 712_01.sql: Prozedur mit XML-Übernahme 7.1.2.2 Verwendung im .NET-Klienten In der Klientenanwendung benötigt man für die Datenstrukturen, die in der Datenbank gespeichert werden und in der Anwendung entweder als Transfer- oder sogar als Geschäftsobjekte in Frage kommen, eine objektorientierte Repräsentation. Wie bei verschiedenen Entwurfsmustern für die Datenzugriffsschicht oder das Domänenmodell kann man hier entweder nur die Felder/Spalten bereitstellen oder auch unmittelbar die Datenzugriffsmethoden und/oder sogar die Geschäftsmethoden programmieren. Da für dieses Beispiel keine Geschäftsmethoden notwendig sind und der Datenzugriff über die Serialisierungs-schnittstelle erfolgt, ist die Klasse Product besonders kurz. Im nachfolgend abgedruckten Quelltext sind darüber hinaus auch noch die öffentlichen Eigenschaften gelöscht. Neben diesen kann man sich also nun noch je nach eingesetzter Schichtung und Programmiertechniken diverse Methoden sowie wenigstens einen Konstruktor denken, welcher für die Eigenschaften Werte entgegen nimmt, wenn das Objekt in der Anwendung gefüllt und nicht aus der Datenbank abgerufen wird. namespace Serialisierung { public class Product /* um get/set gekürzt*/ { private int mProductID; private string mProductNumber; public string ProductNumber private string mName; 283 Fallbeispiele private double mListPrice; private double mStandardCost; } } Serialisierung/Product.cs: Gekürzte objektorientierte Tabellenabbildung In einer Klasse namens ProductSerializer befinden sich nun drei Methoden, von denen zwei den Datenabruf und die dritte die Datenspeicherung/-aktualisierung durchführen. Dabei zeigt die Methode GetProductStandard(), wie die Standarddeserialisierung genutzt wird, um aus einer XML-Struktur wieder ein Objekt zu erstellen. Dabei ist die Entsprechung zwischen XML-Struktur und objektorientierter Übersetzung genau passend, d.h. die Klasse trägt den Namen des XML-Eltern-Elements und die einzelnen Eigenschaften entsprechen den Kind-Elementen dieses Eltern-Elements. Zunächst erstellt man eine Datenbankverbindung, deren Verbindungszeichenkette in einer Eigenschaft der Klasse gespeichert wird. Um das XML aus der DB abzurufen, erstellt man ein XmlDocument-Objekt. Da die Prozedur GetProduct die XML-Daten direkt als relationale Ergebnismenge zurückliefert, kann man mit Hilfe eines XmlReader-Objekts die Daten aus der Prozedur abrufen. Dazu setzt man die ExecuteXmlReader()-Methode von SqlCommand ein. Nachdem man die XML-Nachricht aus der Datenbank eingelesen hat, kann man die eigentliche Deserialisierung mit Hilfe eines XmlSerializer-Objekts durchführen. Sie besitzt eine Deserialize()-Methode, die eine so direkte Entsprechung unmittelbar auflöst. public Product GetProductStandard(string ProductNumber) { XmlDocument document = new XmlDocument(); using (SqlConnection connection = new SqlConnection(ConnectionString)) { SqlCommand cmd = new SqlCommand("Production.GetProduct", connection); cmd.Parameters.Add("@vProductNumber", SqlDbType.NVarChar); cmd.Parameters["@vProductNumber"].Value = ProductNumber; cmd.CommandType = CommandType.StoredProcedure; connection.Open(); 284 Fallbeispiele XmlReader reader = cmd.ExecuteXmlReader(); document.Load(reader); connection.Close(); } XmlSerializer serializer = new XmlSerializer(typeof(Product)); XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None); XmlTextReader read = new XmlTextReader(document.OuterXml, XmlNodeType.Element, context); Product product = (Product)serializer.Deserialize(read); return product; } Serialisierung/ProductSerializer.cs: Standardserialisierung Alternativ zu einem DB-Objekt, das XML-Daten zurückliefert, steht es dem Entwickler natürlich auch frei, mit Hilfe einer FOR XML-Abfrage direkt XML aus der Datenbank abzurufen. Dies verbirgt zwar nicht die Komplexität der Datenstrukturen, ermöglicht aber dennoch die Vorteile des XML-Abrufs zu nutzen. Diese liegen insbesondere in der – wie gerade gesehen – schnellen Erzeugung eines Objekts auch bei vielen Eigenschaften. Die nachfolgende Methode zeigt allerdings neben der direkten Abfrage noch eine andere Alternative, nämlich die Erzeugung eines Objekts aus dem DOM-Dokument bzw. aus dem XmlDocument-Objekt mit individuellem Abruf der XML-Elemente. Man verwendet wie zuvor einen XmlReader, um die XML-Daten aus der Abfrage zu lesen, und die Load()-Methode von XmlDocument, um die Daten zu übernehmen. Die SelectSingleNode()-Methode ist dann die einfachste Lösung, um die einzelnen Knoten in der XML-Struktur auszulesen und die Textknoteninhalte in die Objekteigenschaften zu übernehmen. Bei dieser Variante ist es auf der einen Seite zwar möglich, individuelle Werte zu übernehmen, doch muss man auf der anderen Seite auch für die Datentypzuordnung selbst sorgen. Dies zeigen die verschiedenen TryParse()Verwendungen, um die double- und int-Werte aus den XML-Daten umzuwandeln. public Product GetProductDOM(string ProductNumber) { XmlDocument document = new XmlDocument(); 285 Fallbeispiele String CommandString = @"SELECT * FROM Production.Product2 AS Product WHERE ProductNumber = @vProductNumber FOR XML AUTO, ELEMENTS"; using (SqlConnection connection = new SqlConnection(ConnectionString)) { SqlCommand cmd = new SqlCommand(CommandString, connection); cmd.Parameters.Add("@vProductNumber", SqlDbType.NVarChar); cmd.Parameters["@vProductNumber"].Value = ProductNumber; connection.Open(); XmlReader reader = cmd.ExecuteXmlReader(); document.Load(reader); connection.Close(); } Product product = new Product(); XmlNode root = document.SelectSingleNode("Product"); product.ProductNumber = root.SelectSingleNode( "ProductNumber").InnerText; product.Name = root.SelectSingleNode("Name").InnerText; bool result; double wert; result = double.TryParse(root.SelectSingleNode( "ListPrice").InnerText.Replace(".", ",") , out wert); if (result) { product.ListPrice = wert; } result = double.TryParse(root.SelectSingleNode( "StandardCost").InnerText.Replace(".", ",") , out wert); if (result) { product.StandardCost = wert; } 286 Fallbeispiele int ID; result = int.TryParse(root.SelectSingleNode( "ProductID").InnerText, out ID); if (result) { product.ProductID = ID; } return product; } Serialisierung/ProductSerializer.cs: Eigene DOM-Serialisierung Schließlich müssen die Daten auch wieder aus der Anwendung in die Datenbank zurückgeschrieben werden. Dazu kann man eine entsprechende Prozedur einsetzen, welche die XML-Daten erwartet, zerlegt und neu speichert oder aktualisiert. Dazu ist es lediglich wichtig, die Objekteigenschaften in eine XML-Zeichenkette umzuwandeln, wozu man ein Objekt der Klasse StringWriter verwendet, die bei der Instanziierung ein Objekt vom Typ StringBuilder erwartet. Die Methode Serialize() der Klasse XmlSerializer ist dann in der Lage, eine Zeichenkette in XML-Form zu erzeugen, die unter Verwendung des StringWriter-Objekts eine entsprechende Zeichenkette erzeugt. Diese Zeichenkette übergibt man dann ganz einfach dem Parameter der entsprechenden DB-Prozedur, die sich um die Datenverarbeitung kümmert. public void UpdateProduct(Product product) { XmlSerializer serializer = new XmlSerializer(typeof(Product)); StringBuilder builder = new StringBuilder(); using (StringWriter writer = new StringWriter(builder)) { serializer.Serialize(writer, product); } String ProductXML = builder.ToString(); using (SqlConnection connection = new SqlConnection(ConnectionString)) { SqlCommand cmd = new SqlCommand("Production.UpdateProduct", connection); cmd.Parameters.Add("@vProductXML", SqlDbType.NVarChar); cmd.Parameters["@vProductXML"].Value = ProductXML; 287 Fallbeispiele cmd.CommandType = CommandType.StoredProcedure; connection.Open(); cmd.ExecuteNonQuery(); connection.Close(); } } Serialisierung/ProductSerializer.cs: Eigene DOM-Serialisierung Auch wenn die vorgestellten Methoden bereits sehr gut geeignet sind, auch die Klientensicht vorstellbar zu machen, folgt nun noch in einer anderen Klasse die Main()Methode, mit der die verschiedenen Methoden getestet werden können. Dabei ruft man zunächst mit der Standard-Deserialisierung ein gewöhnliches Produkt ab, ändert seine Eigenschaften und aktualisiert es mit der Serialisierungsmethode, um dann die veränderten Daten mit der DOM-Deserialisierung wieder abzurufen. namespace Serialisierung { class Program { static void Main(string[] args) { ProductSerializer serializer = new ProductSerializer(); Product product = serializer. GetProductStandard("SA-M237"); Console.WriteLine("Standardserialisierung"); Console.WriteLine("-----------------------"); Console.WriteLine( product.Name + " " + product.ProductNumber + " " + product.ListPrice); Console.WriteLine("Aktualisierung"); product.ListPrice = product.ListPrice * 1.4; product.StandardCost = product.StandardCost * 1.4; product.Name = product.Name + " New"; serializer.UpdateProduct(product); Console.WriteLine("-----------------------"); Console.WriteLine("DOM-Serialisierung"); product = serializer.GetProductDOM("SA-M237"); 288 Fallbeispiele Console.WriteLine("DOM - Serialisierung"); Console.WriteLine( product.Name + " " + product.ProductNumber + " " + product.ListPrice); Console.ReadLine(); } } } Serialisierung/Program.cs: Test von De-/Serialisierung 7.2 Datenaustausch Neben der zuvor diskutierten Lösung zur (De-)Serialisierung ist vermutlich die Einrichtung einer Import-/Export-Schnittstelle eine häufigere Aufgabenstellung. Sie soll in diesem Abschnitt diskutiert werden. 7.2.1 Export Neben dieser speziellen Möglichkeit der Serialisierung sind es vor allen Dingen die klassischen CSV-basierten Import-/Export-Schnittstellen, welche durch XMLTechniken abgelöst werden. Zunächst soll die allgemeine Architektur des ExportVorgangs beschrieben werden. Die Grundbausteine des gesamten Systems lassen sich auf der allgemeinen Ebenen der Beschreibung für beide Richtungen nutzen und tauchen daher sowohl für den Import- als auch für den Export-Vorgang auf. In der Datenbank selbst befinden sich verschiedene relationale Tabellen, deren Datenbestand im Hinblick auf den Export-Vorgang die Quelle darstellen. Für dieses Ziel kann zusätzlich auch die Einrichtung einer Sicht mit einer möglichst guten relationalen Abbildung der Zieldatenstruktur oder wenigstens einer zusammenfassenden Struktur hilfreich sein. Diese Sicht ist ebenfalls in der Abbildung oberhalb der beiden Beispieltabellen platziert. In dieser Sicht befinden sich die sich wiederholenden Gruppen von ElternKind-Hierarchien, wenn die beiden Tabellen in einer 1:n-Beziehung zueinander stehen. In einem 1:1-Fall ist dies natürlich nicht der Fall. Hier würden nur die entsprechenden Spalten quasi nebeneinander gestellt. Grundsätzlich muss man unterscheiden, ob der Exportvorgang zu konkreten XMLDokumenten und damit auch Dateien führt, oder ob es sich um reine Datenströme han- 289 Fallbeispiele delt. Während die Datenströme in jedem Fall nur auf Bedarf erzeugt werden und damit nicht notwendigerweise mit Hilfe von zeitgesteuerten DB-Jobs erzeugt werden können, kann hier auch keine zusätzliche Speicherung möglich sein. Daher werden die XMLDatenströme sowohl in Form eines Webservices als auch durch Interaktion mit einem Formular erzeugt und bereitgestellt. Die Dokumente dagegen werden als physische Dateien erzeugt und können auf unterschiedliche Weise in einem öffentlichen Ordner(verzeichnis) platziert und damit erneut persistent gespeichert werden. In vielen Fällen dürften dies auch FTP- oder WebDAV-Ordner sein, die über eine sichere Verbindung genutzt werden können. Die Abbildung gibt keine Informationen darüber, wie die entsprechenden XML-Daten tatsächlich erzeugt werden. Dies ist in einer späteren Abbildung thematisiert. Stattdessen teilt ein senkrechter Strich die gesamte Grafik, um die beiden Hemisphären von zwei Unternehmen zu kennzeichnen. Es muss sich selbstverständlich nicht notwendigerweise um den Austausch von Daten zwischen zwei Unternehmen handeln, sondern es könnten auch Beziehungen zwischen zwei Abteilungen oder allgemein zwischen zwei unterschiedlichen Systemen sein. Auf der Seite von Unternehmen B, welches hier im Rahmen des Export-Vorgangs die Daten erhält, gibt es ebenfalls unterschiedlich Möglichkeiten, wie die Daten abgerufen werden. Dies ist im Gegensatz zur Datenerzeugung sehr wohl dargestellt. 1. 2. 290 Im ersten Fall stellt Unternehmen A auch eine Webanwendung zur Verfügung, welche nach einem entsprechenden Login die Daten quasi zum Download anbietet. Dabei muss man sich diesen Download durchaus nicht so vorstellen, dass tatsächlich Dateien aus einem Ordner abgerufen werden, sondern dass die Benutzeranforderung zu einem Extraktionsvorgang führt, welcher flüchtig einen Datenstrom erstellt. Hierbei liegt die Softwareentwicklung aufseiten von Unternehmen A, dem Datenlieferanten, während Unternehmen B nur die von Unternehmen A bereitgestellten Zugangswege nutzt. In einem zweiten Fall bietet Unternehmen A einen Webservice an. Dies bedeutet zwar auch, dass der wesentliche Teil der Softwareentwicklung bzgl. der Datenverteilung aufseiten von Unternehmen A liegt, doch können die Daten im Normalfall nur über Software von Unternehmen B auch tatsächlich abgerufen werden. Hier interagiert ein Benutzer des Zielsystems mit einem von Unternehmen B erstellten Web- oder Desktopanwendungsformular, welches in Wirklichkeit auf den Webservice zugreift. Diese Variante erlaubt die gleichen Möglichkeiten und Alternativen wie durch das direkt von Unternehmen A angebotene Web-Formular. Auch hier erzeugt Unternehmen A die Exportdaten nicht in Form von Dateien, Fallbeispiele 3. sondern nur als flüchtigen Datenstrom. Hier ist ebenfalls eine Benutzerinteraktion vonnöten, um die Extraktion anzustoßen. Sollte dagegen aus Leistungs- oder Sicherheitsgründen eine solche unmittelbare Interaktion mit dem Exportmodul aufseiten von Unternehmen A unerwünscht sein, so bietet sich dennoch eine Möglichkeit an, über ein selbst erstelltes Formular auf den öffentlichen Ordner von Unternehmen A zuzugreifen. In einer weiteren Variation lässt sich sogar ein Webservice denken, welcher Unternehmen B die Möglichkeit bietet, mit einem solchen Ordner zu interagieren. In einem dritten Fall wird die gerade eben genannte Möglichkeit noch einmal deutlicher aufgegriffen. Hier bietet Unternehmen A einen öffentlichen Ordner auf Basis von FTP, WebDAV oder einem sonstigen Prinzip an, auf den das Unternehmen B entweder direkt, über ein Formular oder auch über eine ImportSoftware zugreift. Insbesondere die letzte Möglichkeit führt eine weitere grundsätzliche Variante in diese Architektur ein. Während in den ersten beiden Fällen zunächst nur ein menschlicher Akteur von Unternehmen B den ImportVorgang für sein Unternehmen durchführte, besteht natürlich sowohl beim Webservice als auch beim öffentlichen Ordner die Möglichkeit, eine eigene Software zu verwenden, welche zeitgesteuert oder auf Basis sonstiger Anstöße und Ereignisse den Importvorgang automatisch durchführt. Die Erzeugung der XML-Daten ist über unterschiedliche T-SQL-Techniken möglich. Sie wurden in den vorherigen Abschnitten erläutert und sollen in diesem Zusammenhang für die Realisierung dieser Import-/Export-Schnittstelle noch einmal aufgeführt werden. Die einzelnen Lösungen haben unterschiedliche Vor- und Nachteile, die teilweise nur auf Grundlage des Zielschemas (Komplexität, Umfang, Tiefe/Hierarchieanzahl) oder der Datenmenge für eine bestimmte Schnittstelle bewertet und letztendlich auch ausgewählt werden können. In allen Fällen hat man die Wahl, ob die erzeugte XML-Struktur als Datei gespeichert oder als XML-Datenstrom zurückgegeben wird. Auf die Möglichkeit, einen Webservice oder einen Windows-Dienst/eine Server-Komponente zu erstellen, wird nicht eingegangen, da dies noch tiefer gehende Kenntnisse der MS SQL Server-Programmierung erfordert. 291 Fallbeispiele Unternehmen A Unternehmen B Datenbank Web-Formular Prozedur <xml-strom/> Funktion<xml-strom/> Benutzer Web Service Sicht XML XML Formular Benutzer Tabelle 1 Tabelle 2 FTP-Verzeichnis / Öffentlicher Ordner Exportsoftware Abbildung 4.3: Funktionsweise des Exports 292 Funktion: Eine Funktion ermöglicht die Erzeugung von XML-Daten mit allen XML-Techniken von T-SQL und liefert die Daten über einen Rückgabewert zurück. Sofern die Daten in einem anderen T-SQL-Programm benötigt werden oder eine Funktion besonders gut zum Einsatz kommen kann, ist dies die richtige Wahl. Dies ist insbesondere der Fall, wenn eine SQL-Anweisung die XML-Daten liefern soll und die äußere Anwendung einen solchen Datentyp verarbeiten kann. Die Übergabe von Parametern für Filter gelingt über die Übergabeparameter. Prozedur: Eine Prozedur liefert die Daten nur über einen Ausgabe-Parameter. Im Normalfall gibt es allerdings eine Vielzahl an äußeren Programmier-sprachen, welche genau diesen Ausgabeparamter viel einfacher abfangen können als den Rückgabewert einer Funktion. Teilweise ist es auch in der äußeren Fallbeispiele Programmiersprache einfacher, eine Prozedur aufzurufen als eine Funktion. Die Übergabe von Parametern für Filter gelingt über die Übergabeparameter. Sicht: Eine Sicht stellt in Form einer virtuellen Tabelle die Möglichkeit dar, XMLDaten so anzubieten, dass man eine einfache Abfrage nutzen kann. In diesem Fall muss allerdings sicher gestellt sein, dass die äußere Anwendung dieses Abfrageergebnis auch verarbeiten kann. Eine Filterung der Daten kann dann über T-SQL durchgeführt werden, was zu umfangreicheren Filtermöglichkeiten führt als eine begrenzte Anzahl an Parametern. Durch den Einsatz von T-SQL für die Filterung können hier fast beliebige Filteranweisungen ad hoc angegeben werden, die ansonsten in einer Prozedur/Funktion von vorneherein vorbereitet sein müssten. 7.2.2 Import Der Import-Vorgang ist nicht nur aus einfach nachzuvollziehenden Gründen das zum Export passenden Pendant. Dieser Umstand wirkt sich auch sehr auf die allgemeine Architektur der Import-/Export-Schnittstelle aus. In der nachfolgenden Abbildung sind lediglich die Pfeile in ihrer Richtung vertauscht, und die Grafik erfordert eine andere Leserichtung, nämlich in diesem Fall von rechts nach links. Bedeutsam ist, dass auf der einen Seite natürlich auch verschiedene Wege genutzt werden können (Export über Webservice, Import dagegen über Upload-Formular), dass aber grundsätzlich auch die Option besteht, beide Vorgänge über den gleichen Kanal durchzuführen, sofern ein wechselseitiger Datenaustausch überhaupt innerhalb des Systemumfangs liegen soll. Die drei oben genannten Schnittstellen sind also in auch in dieser Lösung vorhanden. 1. 2. Im ersten Fall stellt Unternehmen A für den Datenimport eine Web-Anwendung zur Verfügung, welche nach einem entsprechenden Login die Daten für einen UploadVorgang entgegennimmt. Hier wird vermutlich tatsächlich von Unternehmen B eine physische Datei erstellt, die das Exportmodul als Variante auch erst im Rahmen der gesamten Transaktion aus den vorhandenen Daten generiert. In einem zweiten Fall bietet Unternehmen A einen Webservice an. Dies bedeutet zwar auch, dass der wesentliche Teil der Softwareentwicklung bzgl. der Datenverteilung aufseiten von Unternehmen A liegt, doch können die Daten im Normalfall nur über Software von Unternehmen B auch tatsächlich übertragen werden. Hier interagiert ein Benutzer des Zielsystems mit einem von Unternehmen B erstellten Web- oder Desktopanwendungsformular, welches in Wirklichkeit auf den Webservice zugreift. Diese Variante erlaubt die gleichen Möglichkeiten und Alternativen wie durch das direkt von Unternehmen A angebotene Web-Formular. 293 Fallbeispiele Hier erzeugt Unternehmen B die Exportdaten nicht in Form von Dateien, sondern nur als flüchtigen Datenstrom, der als Webservice-Anfrage verschickt wird. Alternativ wird er sogar aus Dateien gewonnen. Hier ist gleichfalls eine Benutzerinteraktion vonnöten, um die Übertragung anzustoßen. Sollte dagegen aus Leistungs- oder Sicherheitsgründen eine solche unmittelbare Interaktion mit dem Importmodul aufseiten von Unternehmen A unerwünscht sein, so bietet sich dennoch eine Möglichkeit an, über ein selbst erstelltes Formular auf den öffentlichen Ordner von Unternehmen A zuzugreifen. In einer weiteren Variation lässt sich sogar ein Webservice denken, welcher Unternehmen B die Möglichkeit bietet, mit einem solchen Ordner zu interagieren und Dateien in diesem Order abzulegen 3. In einem dritten Fall wird die gerade eben genannte Möglichkeit noch einmal deutlicher aufgegriffen. Hier bietet Unternehmen A einen öffentlichen Ordner auf Basis von FTP, WebDAV oder einem sonstigen Prinzip an, auf den das Unternehmen B entweder direkt, über ein Formular oder auch über eine ExportSoftware zugreift. Insbesondere die letzte Möglichkeit führt eine weitere grundsätzliche Variante in diese Architektur ein. Während in den ersten beiden Fällen zunächst nur ein menschlicher Akteur von Unternehmen B den ExportVorgang für sein Unternehmen durchführte, besteht natürlich sowohl beim Webservice als auch beim öffentlichen Ordner die Möglichkeit, eine eigene Software zu verwenden, welche zeitgesteuert oder auf Basis sonstiger Anstöße und Ereignisse den Exportvorgang automatisch durchführt. Die nachfolgende Abbildung zeigt die unterschiedlichen Varianten anhand der vorher schon verwendeten Struktur, wobei die einzige Änderung in der Umkehrung der Pfeilrichtung besteht, die den Datenverkehr symbolisieren. Für die Realisierung einer solchen Import-Schnittstelle lassen sich am besten folgende Techniken nutzen. Die Realisierung über einen Webservice, eine Server-Komponente oder einen Windows-Dienst erfordert fortgeschrittene T-SQL/.NET-Kenntnisse und soll hier nicht behandelt werden. 294 Prozedur: Eine Prozedur erwartet als Übergabeparameter die XML-Daten und zerlegt sie relational. Als Ausgabeparameter kann dann eine Erfolgsmeldung übergeben werden. Im Normalfall ist dies die beste Lösung. Funktion: Eine Funktion erwartet die Daten ebenfalls als Übergabeparameter, kann insbesondere auch eine Erfolgsmeldung als Rückgabewert liefern. Sie muss allerdings in Form eines längeren T-SQL-Skripts genutzt werden und auf der Fallbeispiele rechten Seite der Zuweisung erscheinen, was nicht immer gewünscht/vorteilhaft sein wird. Tabelle: Eine Tabelle kann über einen INSERT-Befehl die XML-Daten erwarten, validieren und dann speichern. Ein Trigger oder eine weitere Prozedur könnte sie dann weiter relational zerlegen. Dies erlaubt die Zwischenspeicherung von XMLDaten zur Kontrolle durch einen Benutzer oder eine Automatik, die nicht unmittelbar mit dem Speichervorgang gleichzeitig abläuft. Unternehmen A Unternehmen B Datenbank Web-Formular Prozedur <xml-strom/> <xml-strom/> Funktion Benutzer Web Service Sicht XML XML Formular Benutzer Tabelle 1 Tabelle 2 FTP-Verzeichnis / Öffentlicher Ordner Importsoftware Abbildung 4.4: Import Die Ausführungen haben vor allen Dingen die einzelnen Wege gezeigt, in denen eine Übertragung von Daten stattfinden kann und mit welchen Werkzeugen sie tatsächlich durchgeführt wird. Diese Werkzeuge wurden in anderen Kapiteln schon weitestgehend 295 Fallbeispiele beschrieben. Sie haben allerdings auch gezeigt, wie viele Untervarianten und zusätzliche Möglichkeiten dem Entwickler bzw. dem Planer offen stehen, eine solche Schnittstelle zu realisieren. Allein solche Überlegungen, wie bei unterschiedlichen ein- oder ausgehenden Datenströmen ein Zwischenformat zu benutzen, eröffnet eine ganz neue Dimension der Architektur, die in Abhängigkeit der zu behandelnden XML-Strukturen sehr viel Arbeit ersparen kann – oder natürlich auch Arbeit bedeuten kann, wenn die Formate und Algorithmen doch nicht so zahlreich werden wie vorausgesetzt. 7.2.3 Beispiele Durch die Tatsache, dass der Datentyp xml an jeder Stelle zum Einsatz kommen kann, an der auch ein anderer Datentyp wie int oder nvarchar zum Einsatz kommen könnte, ergeben sich viele Möglichkeiten, XML-Daten zu liefern, zu speichern oder zu übernehmen Daher sollen die nachfolgenden Beispiele das gesamte Kapitel nur abrunden und die erwähnte Tatsache noch einmal illustrieren. Andere Beispiele, Funktionen, Prozeduren oder Sichten zu erstellen, sind auch schon an anderer Stelle gefallen. Die Möglichkeit, diesen Datentyp als Tabellendatentyp zu verwenden, wurde in jedem Fall mehrfach vorgeführt, sodass dies in diesem letzten Abschnitt ausfallen kann. 7.2.3.1 Sicht Eine Sicht kann XML-Daten zurückliefern, wobei man in diesem Fall zwischen einer Sicht wählen kann, die ausschließlich XML zurückliefert, oder einer solchen, die neben anderen Spalten mit beliebigen Datentypen auch noch eine oder mehrere XML-Spalten besitzt. Das nachfolgende Beispiel zeigt eine Sicht, die ausschließlich XML zurückliefert. In diesem Fall ist nicht viel zu tun, außer eine Abfrage zu erstellen, die das gewünschte XML bereitstellt. Insbesondere die Spalte, in der das XML später abgerufen werden kann, muss namentlich benannt werden, da ansonsten eine Fehlermeldung ausgelöst wird. CREATE VIEW vProductXML (Product) AS SELECT ProductID AS ID, Name, Size, ListPrice FROM Production.Product AS Product FOR XML AUTO, ROOT('Product-List') 723_01.sql: Erstellung einer Sicht Diese Sicht lässt sich dann abfragen, wobei in diesem Fall nur ein nvarchar(max)Datentyp in der Spalte Product erzeugt wird, sodass SELECT Product FROM vPro296 Fallbeispiele ductXML eine lange Zeichenkette und keine Möglichkeit für weitere Einschränkungen bietet. Man erhält als Ergebnis: Product -----------------------------------------------------------<Product-List><Product ID="1" Name="Adjustable Race" ListPrice="0.0000"/>... (1 Zeile(n) betroffen) 7.2.3.2 Prozedur Eine Prozedur kann auf zweierlei Arten mit XML umgehen: entweder liefert sie XML über einen Ausgabeparameter oder eine Abfrage zurück, oder sie ist in der Lage, XML über einen Übergabeparameter zu akzeptieren. In diesem letzten Fall könnte man sich – wie zuvor erläutert – vorstellen, dass mit OPENXML eine relationale Zerlegung in einem Schritt oder mit sonstigen T-SQL-Anweisungen die Zerlegung in mehreren Schritten stattfindet. Die nachfolgend erstellte Prozedur liefert wie eine Sicht XML über eine Abfrage zurück. Wie jeder Prozedur, die eine Ergebnismenge liefert, lässt sich hier sehr einfach ein Parameter für einen Filter übergeben. CREATE PROCEDURE pProductXML (@price money) AS SELECT ProductID AS ID, Name, Size, ListPrice FROM Production.Product AS Product WHERE ListPrice <= @price FOR XML AUTO, ROOT('Product-List') 723_02.sql: Erzeugung einer Prozedur Diese Prozedur kann dann mit EXEC pProductXML @price = 200 getestet werden und liefert ein XML-Dokument (keine Zeichenkette) zurück. Man erhält als Ergebnis: XML_F52E2B61-18A1-11d1-B105-00805F49916B -------------------------------------------------------------<Product-List><Product ID="1" Name="Adjustable Race" ListPrice="0.0000"/>... 297 Fallbeispiele 7.2.3.3 Funktion Eine Funktion kann wie eine Prozedur XML durch einen Übergabeparameter erwarten und verarbeiten; sie kann jedoch auch XML durch ihren Rückgabeparameter zurückliefern. Dies erfordert lediglich, diesen Datentyp anzukündigen und entsprechende Daten über die RETURN-Anweisung auch tatsächlich nach einer Abfrage oder einer schrittweisen Zusammensetzung zurückzuliefern. CREATE FUNCTION fProductXML ( @price money ) RETURNS XML AS BEGIN DECLARE @productList XML SET @productList = ( SELECT ProductID AS ID, Name, Size, ListPrice FROM Production.Product AS Product WHERE ListPrice <= @price FOR XML AUTO, ROOT('Product-List')) RETURN @productList END 723_03.sql: Erzeugung einer Funktion Diese Funktion lässt sich dann über SELECT dbo.fProductXML(1000) testen und liefert wie die Prozedur ein XML-Dokument zurück. 298