Die Daten der Datenbank werden lokal in einem DataSet gespeichert. Dieses Kapitel zeigt, wie Sie (auch ohne Verbindung zur Datenbank) mit den Daten arbeiten – unter Beibehaltung der Konsistenz der Daten. Der Abgleich der lokal geänderten Daten mit der Datenbank ist auch Thema des Kapitels. Der Fall mehrerer Tabellen gleichzeitig wird auch behandelt. Schließlich wird gezeigt, wie die Daten gefiltert werden können.
26 Offline mit DataSet
Die Klasse DataSet nimmt die zentrale Stellung in ADO.NET ein, da Objekte dieses Typs die aus einer Datenbank gelesenen Daten speichern. Organisiert und verwaltet werden die Daten in Form von Tabellen. Wenn Sie sich darunter Tabellen ähnlich denen von MS-Excel vorstellen, liegen Sie gar nicht ganz so falsch. Ob es sich um eine oder auch mehrere Tabellen handelt, hängt von der zugrunde liegenden Abfrage ab, die durch das Command-Objekt beschrieben wird. Enthält das DataSet mehrere Tabellen, können zwischen den Tabellen Beziehungen eingerichtet werden – ganz so wie in der Originaldatenbank.
Im vorletzten Kapitel haben Sie den Typ DataReader und seine provider-spezifischen Variationen kennengelernt. Mit einem Objekt dieses Typs können Sie Daten basierend auf einer Abfrage abrufen. Ein DataReader kann aber nicht die üblichen Aufgaben einer Datenbankanwendung erfüllen. Sie können nur vorwärts navigieren, zudem sind die Daten schreibgeschützt. Damit ist der DataReader in seiner Funktionalität sehr eingeschränkt, aber zum Ausgleich ist er sehr schnell. Ein DataSet hingegen ist im Vergleich dazu deutlich leistungsfähiger.
Die Daten im DataSet stehen in keinem Kontakt zur Datenbank. Nachdem das DataSet über das DataAdapter-Objekt gefüllt worden ist, gibt es zwischen DataSet und Datenbank keine Verbindung mehr. Nimmt ein Anwender Änderungen an den Daten vor, schreiben sich diese nicht in die Originaldatenbank zurück, sondern werden vielmehr lokal im DataSet gespeichert. Das Zurückschreiben der geänderten Daten muss explizit angestoßen weren. Häufig kann man sich dazu wieder des DataAdapters bedienen, der die notwendige Aktualisierungslogik bereitstellt. Sie werden korrekterweise einwenden, dass damit Konfliktsituationen vorprogrammiert sind, wenn ein zweiter Anwender zwischenzeitlich Änderungen am gleichen Datensatz vorgenommen hat. ADO.NET gibt uns aber alle Mittel an die Hand, um eine benutzerdefinierte Konfliktsteuerung und Konfliktanalyse zu codieren. Mit der Aktualisierung der Originaldatenbank werden wir uns im nächsten Kapitel beschäftigen.
Ein DataSet kann aber noch mehr. Sie können mit ihm die Ansicht der Abfrageergebnisse ändern, und Sie können die Daten basierend auf einer oder mehreren Spalten sortieren oder nach bestimmten Kriterien filtern. Außerdem ist die Zusammenarbeit mit XML ausgezeichnet. Der Inhalt eines DataSets kann als XML-Dokument in einer Datei gespeichert werden bzw. kann der Inhalt einer XML-Datei in ein DataSet eingelesen werden. Darüber hinaus lassen sich die Schemainformationen eines DataSets in einer XML-Schemadatei speichern.
26.1 Das DataSet-Objekt verwenden 

26.1.1 Ein DataSet-Objekt erzeugen 

Die Klasse DataSet befindet sich, wie viele andere Klassen, die nicht provider-spezifisch sind, im Namespace System.Data. In den meisten Fällen ist der parameterlose Konstruktor vollkommen ausreichend, um ein DataSet-Objekt zu erzeugen:
Dim ds As DataSet = New DataSet()
Der einfach parametrisierte Konstruktor gibt dem DataSet einen Namen:
Dim ds As DataSet = New DataSet("DSAutoren")
Der Name kann auch über die Eigenschaft DataSetName festgelegt oder abgerufen werden.
26.1.2 Anatomie einer DataTable 

Zum Leben erweckt wird ein DataSet-Objekt nicht durch die Instanziierung der Klasse, sondern vielmehr durch den Aufruf der Fill-Methode des DataAdapters:
...
Dim strSQL As String = "SELECT * FROM Products"
Dim da As DbDataAdapter = New SqlDataAdapter(strSQL, con)
Dim ds As DataSet = New DataSet()
da.Fill(ds)
...
Das Ergebnis der Abfrage enthält alle Datensätze der Tabelle Products in einer Tabelle, die durch ein DataTable-Objekt beschrieben wird. Jeder Spalte, die im SELECT-Statement der Abfrage angegeben ist, entspricht ein Objekt vom Typ DataColumn. Alle Spalten sind in der Eigenschaft Columns vom Typ DataColumnCollection zusammengefasst.
In ähnlicher Weise sind auch die Daten der Abfrage organisiert. Jeder von der Datenbank zurückgelieferte Datensatz wird in einem Objekt vom Typ DataRow gespeichert. Die Auflistung der DataRowCollection enthält alle Datenzeilen in einer Tabelle, auf die Sie über die DataTable-Eigenschaft Rows zugreifen können.
Eine DataTable hat also eine DataColumnCollection und eine DataRowCollection. Da ein DataSet nicht nur eine, sondern beliebig viele Tabellen enthalten kann, muss auch der Zugriff auf eine bestimmte DataTable im DataSet möglich sein. Wie zu erwarten, werden auch alle Tabellen in einem DataSet in einer Auflistung gespeichert. Diese ist vom Typ DataTableCollection, deren Referenz die Eigenschaft Tables des DataSets liefert.
Das klingt alles ziemlich komplex. Die grafische Darstellung in Abbildung 26.1 zeigt den eigentlich einfachen logischen Aufbau.
Abbildung 26.1 Struktur eines DataSet
26.1.3 Zugriff auf eine Tabelle im DataSet 

Wenn ds ein DataSet-Objekt beschreibt, genügt eine Anweisung wie die folgende, um auf eine bestimmte Tabelle im DataSet zuzugreifen:
ds.Tables(2)
Enthält das DataSet mehrere Tabellen, lassen sich die Indizes oft nur schwer einer der Tabellen zuordnen. Wie Sie im letzten Kapitel erfahren haben, weist der DataAdapter den Tabellen im DataSet auch Bezeichner zu (Table, Table1, Table2 usw.). Sowohl die Indizes als auch die Standardbezeichner sind aber wenig geeignet, um den Code gut lesbar zu gestalten. Das Kapitel beschreibt, wie mit einer DataTableMappingCollection sowie einer DataColumnMappingCollection lesbare Tabellen- und Spaltennamen zugeordnet werden. Sie sollten diese Angebote nutzen, denn die Anweisung
ds.Tables("Artikel")
wird Ihnen später eher dabei helfen, den eigenen Programmcode zu verstehen, als die Angabe eines Index, der sich nur schlecht zuordnen lässt.
26.1.4 Zugriff auf die Ergebnisliste 

Ein DataRow-Objekt stellt den Inhalt eines Datensatzes dar und kann sowohl gelesen als auch geändert werden. Um in einer DataTable von einem Datensatz zum anderen zu navigieren, benutzen Sie deren Eigenschaft Rows, die die Referenz auf das DataRowCollection-Objekt der Tabelle zurückgibt. Dieses Objekt enthält alle Datensätze, die die Abfrage zurückgibt. Die einzelnen DataRows sind über den Index der Auflistung adressierbar.
Mit der folgenden Anweisung wird der Verweis auf die dritte Datenzeile in der Tabelle Artikel des DataSets namens ds der Variablen row zugewiesen:
Dim row As DataRow = ds.Tables("Artikel").Rows(2)
Über die Indexierung können Sie den Inhalt einer oder mehrerer Spalten der betreffenden Datenzeile auswerten. Dem Indexer übergeben Sie entweder den Namen der Spalte, deren Index in der DataColumnCollection der DataTable (die Ordinalposition) oder eine Referenz auf die gewünschte Spalte. Damit sind
ds.Tables(0).Rows(4)("ProductName")
ds.Tables(0).Rows(4)(1)
gültige Ausdrücke, um den Inhalt einer Datenzelle auszuwerten. Der Rückgabewert ist immer vom Typ Object und enthält die Daten der angegebenen Spalte. Häufig ist eine anschließende Konvertierung in den richtigen Datentyp notwendig.
Die Indexierung über den Spaltenbezeichner bezieht sich immer auf die Spaltenzuordnungen in der DataColumnMappingCollection. Die implizite Zuordnung reicht die Spaltenbezeichner der Originaldatenbank an die DataTable weiter, sodass Sie ohne eigene Definitionen diese im Indexer verwenden können. Sonst sind es die von Ihnen definierten Spaltennamen. Dazu wollen wir uns ein Beispiel ansehen.
'...\ADO\DataSet\DataRows.vb |
Option Strict On
Imports System.Data.Common, System.Data.SqlClient
Namespace ADO
Module DataRows
Sub Test()
Dim con As DbConnection = New SqlConnection()
con.ConnectionString = "Data Source=(local);" & _
"Initial Catalog=Northwind;Integrated Security=sspi"
Dim cmd As DbCommand = New SqlCommand()
cmd.CommandText = _
"SELECT ProductName, UnitPrice FROM Products WHERE UnitsOnOrder > 0"
cmd.Connection = con
Dim ds As New DataSet()
Dim da As DbDataAdapter = New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds, "Artikel")
Dim tbl As DataTable = ds.Tables("Artikel")
For no As Integer = 0 To tbl.Rows.Count – 1
Console.WriteLine("{0,-35}{1}", _
tbl.Rows(no)("ProductName"), tbl.Rows(no)("UnitPrice"))
Next
Console.ReadLine()
End Sub
End Module
End Namespace
Gefragt ist nach allen Artikeln, zu denen aktuell Bestellungen vorliegen. Nach dem Füllen des DataSets wird die Ergebnisliste in einer Schleife durchlaufen. Der Schleifenzähler wird dabei als Index der Datenzeile genutzt. Um die Anweisungen kurz zu halten, wird vor Beginn des Schleifendurchlaufs die DataTable im DataSet in einer Variablen gespeichert.
Dim tbl As DataTable = ds.Tables("Artikel")
Da alle Datenzeilen von einer Auflistung verwaltet werden, stehen die üblichen Methoden und Eigenschaften zur Verfügung. In diesem Code wird die Eigenschaft Count abgefragt, um festzustellen, wie viele Datenzeilen sich in der Ergebnisliste befinden.
Sie können auch statt der For-Schleife eine For Each-Schleife einsetzen. Der folgende Codeausschnitt ersetzt daher vollständig die For-Schleife unseres Beispiels:
For Each row As DataRow in tbl.Rows
Console.WriteLine("{0,-35}{1}", row("ProductName"), row("UnitPrice"))
Next
26.1.5 Dateninformationen in eine XML-Datei schreiben 

Sie können die Dateninformationen eines DataSets in eine XML-Datei schreiben und später wieder laden. Hierzu stehen Ihnen in DataSet mit WriteXml und ReadXml zwei Methoden zur Verfügung. Beiden Methoden übergeben Sie als Parameter den Ort, an dem die Daten gespeichert bzw. aus dem die XML-Daten gelesen werden sollen, zum Beispiel:
ds.WriteXml("C:\Daten\ContentsOfDataset.xml")
...
ds.ReadXml("C:\Daten\ContentsOfDataset.xml")
Der Parameter beschränkt sich nicht nur auf Dateien. Sie können auch einen TextReader, einen Stream oder einen XmlReader angeben.
Nachfolgend sehen Sie den Teilausschnitt eines XML-Dokuments, dem die Abfrage
SELECT ProductID, ProductName FROM Products
zugrunde liegt.
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<Table>
<ProductID>1</ProductID>
<ProductName>Chai</ProductName>
</Table>
<Table>
<ProductID>17</ProductID>
<ProductName>Alice Mutton</ProductName>
</Table>
<Table>
<ProductID>3</ProductID>
<ProductName>Aniseed Syrup</ProductName>
</Table>
<Table>
<ProductID>40</ProductID>
<ProductName>Boston Crab Meat</ProductName>
</Table>
<Table>
<ProductID>60</ProductID>
<ProductName>Camembert Pierrot</ProductName>
</Table>
...
</NewDataSet>
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.