Mittwoch, 23. Mai 2012


Artikel

Juni 2005 | Artikel

Änderungswünsche

(Link zum Artikel: http://www.entwickler.de/dotnet//000280)

Änderungen an Daten mit XML-Diffgrams speichern

Text: von Jörg Krause
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
XML wird bereits in vielen Applikationen eingesetzt. Eine typische Aufgabe ist das Ablegen von Konfigurationsdaten im XML-Format. In anderen Fällen werden hierarchische Daten gespeichert. Änderungen an Daten oder Konfigurationen sind jedoch manchmal nicht permanent vorzunehmen. In solchen Fällen wäre es hilfreich, nur die Unterschiede zum originalen Datenbestand aufzuzeichnen. Damit die Applikation unverändert funktioniert, gibt es eine Technik, beim Abruf Originaldaten und Änderungsanweisungen zu verschmelzen.

Das XML-Speicherformat der Änderungsdaten wird als Diffgram bezeichnet. Der Umgang damit erscheint auf den ersten Blick kompliziert, weshalb es offensichtlich erst wenige praktische Anwendungen gibt. Dabei sind die Grundlagen dazu schon einige Jahre alt und das Format selbst ist in seinen Grundzügen eine Norm. Korrekt handelt es sich eigentlich um die XML Diff Language (XDL), der Begriff Diffgram ist lediglich eine Umschreibung, die überwiegend von Microsoft gebraucht wird. Eine W3C-Norm ist Diffgram noch nicht, auch wenn es offensichtlich Überlegungen gibt, XDL entsprechend zu etablieren. Es gibt darüber hinaus mehrere auf XML und proprietären Formaten basierende Differenzsprachen. Es bleibt daher abzuwarten, wie ein endgültiger Standard aussehen wird. Dem aktuellen Einsatz steht jedoch nichts entgegen, denn die Anwendung beschränkt sich auf ein lokales System. Zum Datenaustausch mit Dritten waren Diffgrams nie vorgesehen, sodass spätere Kompatibilitätsprobleme kaum zu befürchten sind.

Ausgangspunkt zur Erzeugung eines Diffgrams sind immer zwei Zustände einer Datenquelle. Das Diffgram selbst enthält Daten, die die Unterschiede repräsentieren. Es ist durch entsprechende Anweisungen möglich, Einfügungen, Änderungen und Löschungen zu speichern.

Ein wenig XML
Innerhalb der .NET-Welt ist die Programmierung von Diffgrams erstaunlich einfach. Es ist jedoch ratsam, sich ein wenig mit den Grundlagen auseinander zu setzen, sei es zur Fehlersuche oder aus rein technischem Interesse. Listing 1 zeigt das Skelett eines Diffgrams.



Listing 1
  1. <?xml version="1.0"?>
  2. <diffgr:diffgram
  3. xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
  4. xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
  5. xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  6. <DataInstance>
  7. ...
  8. </DataInstance>
  9. [<diffgr:before>
  10. ...
  11. </diffgr:before>]
  12. [<diffgr:errors>
  13. ...
  14. </diffgr:errors>]
  15. </diffgr:diffgram>
Das Element <DataInstance> steht als Platzhalter für die Dateninstanz, deren Änderungen aufgezeichnet wurden. Handelt es sich dabei um XML, ist es meist das Wurzelelement des XML-Baums. In diesem Zusammenhang sollte beachtet werden, dass XML das interne Speicherformat der DataSet-Objekte in ADO.NET ist, das auch klassische relationale Tabellen abpictureet. Der Aufbau dieser Daten prägt letztlich das exakte Erscheinungspicture der Diffgrams. Wenn Sie dem DataSet einen Namen geben, bestimmt diese Eigenschaft den Namen der Dateninstanz. Da ein DataSet mehrere Tabellen enthalten kann, spielt deren Namen hier noch keine Rolle.

Es folgt nach der Festlegung der Datenquelle ein Block innerhalb des Elements <diffgr:before>. Hier sind die Daten aufgeführt, die geändert wurden, also gelöscht oder modifiziert. Die oberste Ebene innerhalb dieses Elements pictureen die Tabellennamen. Es folgt noch - optional - eine Block mit dem Element <diffgr:errors>. Hier werden Fehler aufgezeichnet, die bei der Ermittlung der Datendifferenzen auftraten. Normalerweise sollte dieser Block fehlen.

Die Elemente enthalten mehrere Attribute, mit denen die eigentlichen Änderungen koordiniert werden. Dazu gehört an erster Stelle id, ein Verweis auf das zu ändernde Element der Dateninstanz. Die Art der Änderung wird mit hasChanges festgestellt; es kann die Parameter inserted oder modified enthalten. Zusätzlich kann noch parentID auftreten, durch die das ausführende Programm Kind-Eltern-Beziehungen zwischen Elementen erkennt und die Reihenfolge der Verarbeitung steuert.
Die Logik dahinter
Um Diffgrams zu verstehen, muss man sich mit der Logik zur Feststellung der Änderungen vertraut machen. Das Einfügen eines Datensatzes wird vorgenommen, wenn das Element im Instanz-Block nicht innerhalb der Elemente unterhalb <diff:before> erscheint und das Attribut diffgr:hasChanged=inserted besitzt. Änderungen an den Daten führt der Prozessor aus, wenn das Element im Instanz-Block existiert und das Attribut diffgr:hasChanged=modified vorhanden ist. An dieser Stelle kann ein Fehler auftreten, denn wenn das Element nicht existiert und dennoch geändert werden soll, läuft die Aktion ins Leere. Das passiert, weil Diffgrams selbst nicht die Konsistenz der Daten sicherstellen. Der Instanz-Block ist in der Regel nur ein Verweis auf eine externe Datenquelle, die XML-Stammdaten. Lediglich hinzugefügte Daten erscheinen hier direkt. Das Löschen basiert auf der Auflistung von Elementen innerhalb <diffgr:before>, wobei das Attribut diffgr:hasChanged fehlt. Ein spezielles Attribut gibt es nicht.

In der Praxis ist der Umgang natürlich nicht damit verbunden, selbst diese Tags aufzuschreiben oder Programme zu entwickeln, die dies tun. Dank .NET ist der Umgang mit Diffgrams sehr einfach. Eine kleine WinForms-Applikation soll als Ausgangspunkt für die ersten eigenen Versuche dienen.
Die Macht des Benutzers
Gute Software zeichnet sich vor allem durch Benutzerfreundlichkeit aus. Dazu gehört zum einen ein stabiler Betrieb, zum anderen eine gute Anpassbarkeit an Benutzerwünsche. Hat dann der Anwender seine Applikation vollkommen kaputt konfiguriert, sollte ein Klick auf Wiederherstellen oder ein Neustart den Urzustand wieder hervorbringen. Es ist nun naheliegend, hierfür XML und Diffgrams einzusetzen. Die eigentliche Konfiguration wird mit einer XML-Datei vorgenommen, was heute bereits bei vielen Applikationen zum Standard gehört. Oft wird der Anwender oder Administrator jedoch gezwungen sein, Änderungen direkt in dieser XML-Datei vorzunehmen und sich selbst um Sicherheitskopien zu kümmern.
Das Beispiel in Abpictureung 1 bietet einen einfachen Dialog zur Auswahl von Anredeformen an. Diese sind in einer XML-Datei gespeichert, wie sie mit Visual Studio .NET schnell erstellt ist. Listing 2 zeigt die Ausgangsvariante. Dem Benutzer soll es nun überlassen werden, Elemente aus der Liste zu entfernen und neue hinzuzufügen. Die Speicherung erfolgt nicht im Original, sondern in einem Diffgram Custom.xml.



Listing 2
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <selectboxes xmlns="http://tempuri.org/SelectBoxes.xsd">
  3. <box name="Box1">
  4. <option d3p1:option="1" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Ihre Auswahl</option>
  5. <option d3p1:option="2" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Frau</option>
  6. <option d3p1:option="3" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Herr</option>
  7. <option d3p1:option="4" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Frau Dr.</option>
  8. <option d3p1:option="5" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Herr Dr.</option>
  9. <option d3p1:option="6" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Herr Prof.</option>
  10. <option d3p1:option="7" xmlns:d3p1="http://tempuri.org/SelectBoxes.xsd">Frau Prof.</option>
  11. </box>
  12. </selectboxes>
Wenn Sie eine solche XML-basierte Applikation praktisch entwickeln, ist es sinnvoll, mit typisierten DataSets zu arbeiten. Deshalb besteht beim Neuentwurf der erste Schritt darin, eine Schema-Datei zu erzeugen. Im Studio geht dies sehr einfach über das Menü XML | Schema erzeugen. Das Menü erscheint, wenn eine XML-Datei im Editor geöffnet wurde.

Nach dieser Vorbereitung geht es nun an die Entwicklung der Testapplikation. Abpictureung 1 zeigt das Ergebnis. Im Code selbst sind drei Steuerelemente von Bedeutung: button1 ist die Schaltfläche zum Entfernen des ausgewählten Eintrags, comboBox1 die Combo-Box zur Anzeige der Liste und zum Eintragen neuer Daten und Label1 dient zur Ausgabe von Statusmeldungen.
Daten aus XML holen
Der Konstruktor der Klasse Form1, die hier verwendet wird, enthält die nötigen Anweisungen zum Lesen der XML-Daten. Benötigt wird dazu ein DataSet:
  1. DataSet dsLists = new DataSet();
Nun werden diesem die Daten aus der XML-Datei übergeben:
  1. dsLists.ReadXmlSchema (@\data\SelectBoxes.xsd);
  2. dsLists.ReadXml (@\data\SelectBoxes.xml);
Dann wird der Zustand nach dem Lesen der Originaldaten festgehalten. Auf Basis dieses Zustandes werden später die Differenzen im Diffgram errechnet.
  1. dsLists.AcceptChanges();
Jetzt sollen natürlich eventuell vorliegende Daten aus einem Diffgram angewendet werden. Da beim ersten Start die entsprechende Datei vermutlich nicht vorliegt, ist ein Test angebracht:
  1. if (FileExists (@\data\Custom.xml)
  2. {
  3. dsLists.ReadXml(@\data\Custom.xml, XmlReadMode.DiffGram)
  4. }
Das Geheimnis steckt hier in dem zweiten Parameter: XmlReadMode.DiffGram. Dadurch werden die Daten als Änderungswunsch interpretiert und ausgeführt. Der weitere Verlauf entspricht dem üblichen Vorgehen, um den Inhalt eines DataSets an ein Steuerelement zu binden. Die Datensicht (DataView) wird benötigt, weil die Struktur der XML-Datei bereits so ausgelegt wurde, dass mehrere Steuerelemente mit einer Datei verwaltet werden können. Die hier verwendete Box1 ist zwar die einzige, bei Erweiterung kann jedoch so die Unterscheidung getroffen werden:
  1. DataView dv = new DataView ();
  2. dv.Table = dsLists.Tables["Option"];
  3. boxId = dsLists.Tables["box"].Select("name='Box1'")[0]["box_Id"].ToString();
  4. dv.RowFilter = "box_Id = " + boxId;
  5. comboBox1.DataSource = dv;
  6. comboBox1.DisplayMember = "option_Text";
  7. comboBox1.ValueMember = "option";
Geben und nehmen
Nach der ersten Initialisierung geht es nun darum, auf die möglichen Benutzeraktionen zu reagieren. Da am Anfang des Programms die Methode AcceptChanges aufgerufen wurde, werden alle Änderungen wie in einem Protokoll automatisch aufgezeichnet. Zwei mögliche Prozesse sind zu verarbeiten: Bei jedem Auslösen der Enter-Taste in der Combobox soll der dann erfasste Text der Liste persönlicher Änderungen hinzugefügt werden. Dazu wird auf das KeyPress-Ereignis reagiert und der Code der Enter-Taste ((char) 13) erkannt:
  1. private void comboBox1_Enter(object sender, System.Windows.Forms.KeyPressEventArgs e)
  2. {
  3. if (e.KeyChar == (char) 13)
  4. {
  5. string newEntry = comboBox1.Text;
  6. dr = dsLists.Tables["option"].NewRow ();
  7. dr["option_Text"] = newEntry;
  8. dr["box_Id"] = boxId;
  9. dsLists.Tables["option"].Rows.Add (dr);
Der Vorgang unterscheidet sich kaum vom Umgang mit DataSet-Objekten in anderen Fällen. Zuerst wird eine neue Reihe dr mit NewRow() erzeugt. Dieser wird der Wert der Combobox zugewiesen. Dann wird die fertige Reihe der Tabelle hinzugefügt (Rows.Add(dr)). Um nun die Differenzen erkennen und sichern zu können, wird ein weiteres DataSet benutzt, im Beispiel hat es den Namen ds. Diesem werden die bislang erkannten Änderungen übertragen, ermittelt mit GetChanges:
  1. ds = dsLists.GetChanges ();
  2. sw = new StreamWriter(@"..\data\Custom.xml");
  3. ds.WriteXml(sw, XmlWriteMode.DiffGram);
  4. sw.Close();
  5. comboBox1.Refresh ();
  6. }
  7. }
Anschließend sorgt die Methode WriteXml mit dem Parameter XmlWriteMode.DiffGram dafür, dass die Daten geschrieben werden. Damit ist der Vorgang schon erledigt. Da das ursprüngliche DataSet auch alle Änderungen speichert, genügt ein Refresh der Combobox, um die Anzeige zu aktualisieren. Tatsächlich findet ein weiterer Zugriff auf die Diffgram-Datei im weiteren Verlauf des Programms nur schreibend statt. Das DataSet ist die permanent verwendete und im Speicher schnell erreichbare Datenquelle. Lediglich beim Programmstart werden die früher angelegten Änderungen eingelesen. Dieses Verfahren ist auch bei sehr großen Datenmengen effizient.

Kaum aufwändiger ist das Entfernen eines Elements aus der Liste. Die entsprechende Ereignisbehandlungsmethode beginnt mit einer Sicherheitsabfrage, damit keine leeren Einträge gespeichert werden:
  1. private void button1_Click(object sender, System.EventArgs e)
  2. {
  3. if (dsLists.Tables["option"].Rows.Count == 0 || comboBox1.SelectedValue == null)
  4. {
  5. comboBox1.Text = "?";
  6. LabelSelection.Text = "Keine Elemente mehr vorhanden";
  7. LabelSelection.ForeColor = Color.Red;
  8. }
  9. else
  10. {
Nun wird anhand des ausgewählten Wertes mit Find die Reihe ermittelt, die gelöscht werden soll. Da nichts weiter damit passiert, wird die Löschmethode Delete gleich angehängt:
  1. dsLists.Tables["option"].Rows.Find(comboBox1.SelectedValue).Delete();
Auch hier werden wieder alle Änderungen festgestellt. Dieser Vorgang ermittelt immer den aktuellen Zustand des DataSets seit dem Programmstart. Dadurch bläht sich die Diffgram-Datei nicht auf, wenn ein und derselbe Datensatz immer wieder entfernt und erneut hinzugefügt wird. Das Verfahren muss aber nicht in allen Fällen anwendbar sein. Die Methode GetChanges ermittelt wieder die Datendifferenzen und das Schreiben besorgt WriteXml:
  1. ds = dsLists.GetChanges ();
  2. sw = new StreamWriter(@"..\data\Custom.xml");
  3. ds.WriteXml(sw, XmlWriteMode.DiffGram);
  4. sw.Close();
  5. }
  6. comboBox1.Refresh ();
  7. }
Werden nun Einträge aus der Liste entfernt oder welche hinzugefügt, erscheint sofort die Datei Custom.xml. Diese Datei ist der Beweis für den Erfolg der Aktion. Wird das Programm beendet und später neu gestartet, bleiben alle Änderungen erhalten. Ein Löschen der Custom.xml ist jederzeit möglich - auch bei laufendem Programm. Es lässt die Anzeige sofort wieder in den Urzustand zurückfallen. Wird das Löschen mit einer Schaltfläche verknüpft, hätte der Benutzer so eine Möglichkeit, das Programm per Klick wieder in den Anfangszustand zu versetzen.
Diffgrams im SQL Server 2000
Ein direkter und einfacher Weg, den SQL Server 2000 einzubeziehen, besteht in der Nutzung der entsprechenden SQL-Klassen und der XML-Unterstützung des DataSets. In der Praxis ist das jedoch möglicherweise nicht ausreichend leistungsfähig. Besser wäre es, wenn die XML-Operationen direkt im SQL Server stattfinden, der Ansatz clientseitiger Datenverarbeitung also vermieden wird. Standardmäßig können mit dem SQL-Provider des Frameworks nur klassische Abfragen verarbeitet werden, jedoch nur eingeschränkt Aufrufe mit der SQLXML-Erweiterung FOR XML. SQLXML 3.0 bringt nicht nur einen verbesserten Zugriff auf die Ergebnisse der FOR XML-Anweisung. Der Zugriff auf relationale Tabellen ist nun per XPath möglich, was manchmal eleganter und einfacher ist. Der spannende Punkt ist aber: Der SQL Server lernt den Umgang mit Diffgrams. Diese sind nicht nur ebenso einfach anwendbar wie in den zuvor gezeigten Beispielen angedeutet, sondern mit denen des .NET-Framework kompatibel. Insofern gibt es außer diesem Umstand wenig Neues zu berichten. Bleibt die Beschaffungsfrage zu klären.

Der erste Schritt besteht darin, die SqlXml-Klassen verfügbar zu machen. Dazu ist das entsprechende Paket SQLXML 3.0 SP1 von der Microsoft-MSDN-Website herunterzuladen. Die Installation setzt den SQL Server 2000 bzw. auf einem anderen System die Client-Installation voraus. Erst danach kann die SQLXML-Erweiterung installiert werden.

Nach der Installation liegt die Datei Microsoft.Data.SqlXml.dll im Verzeichnis /Programme/SQLXML 3.0/bin ihres Systemlaufwerks. Wird Visual Studio .NET verwendet, sollte im Projekt eine Referenz zu dieser Datei eingerichtet werden. Kommt dagegen der Kommandozeilen-Compiler zum Einsatz, ist der Schalter /reference:Microsoft.Data.SqlXml.dll beim Übersetzen erforderlich. Außerdem müssen auf dem verwendeten Computer entweder der SQL Server 2000 oder dessen Client-Services installiert sein.

Nach dieser Vorbereitung ist der entsprechende Namensraum verfügbar:
  1. using Microsoft.Data.SqlXml;
Die Präsentation des gesamten Funktionsumfangs würde hier zu weit führen. Die entsprechende Dokumentation ist im Installationspaket enthalten. Interessant ist die Möglichkeit, Diffgrams direkt an den SQL Server zu senden. Dabei spielt es keine Rolle, ob diese auch von diesem erzeugt wurden oder in anderem Zusammenhang standen. Durch die Verwendung der XML-Schemas kann sichergestellt werden, dass die Datenstrukturen im DataSet identisch sind. Über die Diffgrams hinaus lassen sich auch so genannte Updategrams entwerfen, die komplexere Änderungen an Tabellenstrukturen erlauben. Sie lösen ein altes Problem auf neue Weise: die Aktualisierung einer Datenbank im laufenden Betrieb.

In Anbetracht der Fähigkeiten des Frameworks mag es erstaunen, dass SQLXML vergleichbare Klassen zusätzlich liefert. Dies hat zwei Hintergründe. Zum einen kann die Programmierung von Diffgrams auch vom alten ADO und allen anderen COM-Applikationen aus vorgenommen werden. Zum anderen erfolgt die Verarbeitung im SQL Server und damit in der Datenschicht einer Mehrschichtapplikation. In einer verteilten Umgebung kann dies die Clients signifikant entlasten. Ob das praktikabel ist, hängt von der Anwendung ab. Während .NET hier keine Wahl lässt, erweitert der Einsatz des SQL Servers und seiner XML-Fähigkeiten die Lösungswege.

Und noch eine Funktion erweitert das Einsatzspektrum der Diffgrams. Mit dem SQLXML 3.0-Pack wird eine ISAPI-Erweiterung installiert, die das Ausführen von Diffgrams direkt im Browser erlaubt. Gestartet wird eine XML-Datei, die - wenn sie als Diffgram erkannt wird - mit den entsprechenden Parametern an den SQL Server geleitet wird. Eine optionale Transformation produziert parallel dazu das Ergebnis für den Betrachter.

Einen Schritt weiter gehen die SOAP-Erweiterungen im Paket, die diese Technik mit Web Services kombinieren und damit den Abgleich von Datenbanken über reine Web-Techniken in besonders effizienter Weise erlauben.
Fazit
Diffgrams - egal ob direkt programmiert oder im SQL Server 2000 ausgeführt - sind eine effiziente Möglichkeit, Änderungen an Datenbeständen zu verfolgen und persistent zu machen. Dabei bietet das .NET Framework elegant zu programmierende Klassen, die nur wenig zusätzliche Arbeit verlangen. Einer weiteren XML-isierung von Applikationen steht nichts mehr im Wege. Mit etwas Fantasie lassen sich sogar clevere Kombinationen von XML-basierten Technologien finden. Warum nicht mal ein Diffgram per SOAP verschicken und entfernte Datenbanken über einen Web Service aktualisieren?

Kommentare