Sie sind auf Seite 1von 35

und C#

Datenbankzugriff mit ADO.NET

Ausarbeitung
von
Patrick Pöndl
Kaiser-Wilhelm-Ring 76
55118 Mainz
patrick@poendl.de

Dem Fachbereich Mathematik und Informatik


der Johannes Gutenberg-Universität Mainz
vorgelegt im Januar 2005.

Themenstellung: Prof. Dr. J. Perl · Betreuer: T. Hillebrand


.NET und C# · Datenbankzugriff mit ADO.NET 1 · ADO.NET · Eine Einführung

1. ADO.NET · Eine Einführung


ADO.NET ist die Datenbank-Schnittstelle des .NET-Frameworks. Sie ermöglicht den
objektorientierten Zugriff sowohl auf relationale Datenbanken als auch hierarchische
XML-Informationen.

ADO.NET ist der Nachfolger von ADO (ActiveX Data Objects). Trotz der Ähnlichkeit
des Namens ist der Technologiesprung enorm. So fällt bei ADO.NET die Unterteilung
in die Programmierschnittstellen ADO und OLEDB weg, es gibt nur noch eine univer-
selle Schnittstelle für alle .NET-Sprachen. Geändert hat sich auch das Objektmodell. An
die Stelle der ADO-RecordSet-Klasse treten jetzt die Klassen DataSet, DataTable und
DataReader.

Zentrales Element ist das DataSet, das mehrere Tabellen aufnimmt und verknüpft sowie
auch Beziehungen zwischen den enthaltenen Tabellen herstellen kann. Das DataSet
arbeitet wie ein Zwischenspeicher und schreibt geänderte Daten wieder in die Daten-
quellen zurück.

Der Zugriff auf die Daten erfolgt über so genannte .NET Data Provider (Managed Pro-
vider), sprich einer Art Treiber für unterschiedliche Datenquellen. Zum Einlesen der
Informationen ist eine eigene Klasse nötig, ein so genannter DataAdapter, der in jedem
Data Provider speziell für die jeweilige Datenquelle implementiert ist (z.B. OLEDB,
SQL). Die DataAdapter stellen die Verbindung zu den tatsächlichen Datenbanken her,
füllen das DataSet mit Informationen oder schreiben veränderte Datensätze zurück.
Nach dem Einlesen der Daten trennt ADO.NET die Verbindung zur Datenquelle.

Da ein DataSet auch XML lesen und bearbeiten kann, ist das Einlesen von XML-
Dateien in eine Datenbank unproblematisch. Dank XML ist es auch möglich, Daten via
HTTP und SOAP zu transportieren. Ein DataSet kann damit auch Informationen durch
eine Firewall schleusen. Dies war beim klassischen ADO, das COM-Marshalling ver-
wendet, nur mit großem Aufwand möglich.

Vor der näheren Betrachtung von ADO.NET werfen wir zunächst einen kurzen Blick
zurück auf die Entwicklung der Datenbankzugriffstechnologien in den letzten Jahren.

1
.NET und C# · Datenbankzugriff mit ADO.NET 2 · Datenbankzugriffstechnologien

2. Datenbankzugriffstechnologien und ihre Entwicklung


Zu Beginn erfolgte der von Anwendungen ausgehende Datenbankzugriff mittels nativer
Bibliotheken wie beispielsweise der DBLib für SQL-Server. Dies ließ zwar schnellen
Datenbankzugriff zu, weil keine Extraschicht beteiligt war, bedeutete jedoch auch, dass
Entwickler einen eigenen Satz von APIs für jedes Datenbanksystem erlernen mussten,
mit dem sie zu tun hatten. Im Falle einer Anwendungsaktualisierung musste meist der
gesamte betroffene Programmcode geändert werden, damit die Anwendung mit einem
anderen Datenbanksystem kompatibel war.

2.1 ODBC
Als Reaktion auf diese Probleme entwickelten Microsoft und andere Firmen in den frü-
hen neunziger Jahren ODBC (Open Database Connectivity). ODBC stellt eine allge-
meine Datenzugangsschicht zur Verfügung, mit der der Zugriff auf fast jedes RDBMS
(relationales Datenbankmanagementsystem) möglich ist. ODBC benutzt einen RDBMS-
spezifischen Treiber, um mit der Datenquelle zu kommunizieren. Die Treiber werden
vom ODBC-Treibermanager geladen und gehandhabt. Aufgrund der Kommunikation
der Anwendung mit dem Treibermanager durch eine Standard-API sollte theoretisch die
Veränderung der Verbindungseigenschaften ausreichen, um auf ein anderes RDBMS
zuzugreifen. In der Praxis gibt es allerdings häufig auch Unterschiede zwischen den
jeweiligen SQL-Dialekten, die dies erschweren. Die wahrscheinlich wichtigste Errun-
genschaft von ODBC ist die Tatsache, dass es ein offener Standard ist. Infolgedessen
sind ODBC-Treiber für viele Datenbanksysteme entwickelt worden, die nicht direkt
durch neuere Datenzugangstechnologien erreicht werden können. Aus diesem Grund
spielt ODBC auch in Verbindung mit ADO.NET immer noch eine gewisse Rolle.

2.2 DAO
Problematisch an ODBC ist, dass es hauptsächlich für Sprachen wie C++ entworfen
wurde. Als Visual Basic an Bedeutung gewann, bedurfte es einer Datenzugriffstechno-
logie, die auf natürlichere Weise von Visual Basic verwendet werden konnte. Daher
wurde mit dem Erscheinen von Visual Basic 3 DAO (Data Access Objects) eingeführt.
DAO stellt auch heute noch die schnellste Möglichkeit dar, mit Visual Basic auf Micro-
soft Access zuzugreifen.

2.3 RDO
Wegen seiner Optimierung für Microsoft Access war DAO bei ODBC-Datenquellen
sehr langsam. Als Lösung präsentierte Microsoft im Rahmen des Releases der 32-bit-
Version von Visual Basic 4 RDO (Remote Data Objects). RDO stellt ein einfaches
Objektmodell zur Verfügung – ähnlich dem von DAO, allerdings mit spezifischem De-
sign für den Zugriff auf ODBC-Datenquellen.

2.4 OLE DB

2
.NET und C# · Datenbankzugriff mit ADO.NET 2 · Datenbankzugriffstechnologien

Das Release von OLE DB bescherte der Welt der Datenzugriffstechnologien die nächs-
te große Veränderung. Architektonisch hat OLE DB eine gewisse Ähnlichkeit mit
ODBC: Die Kommunikation mit der Datenquelle findet durch die OLE-DB-Provider
(konzeptionell den ODBC-Treibern ähnlich) statt, die für jeden unterstützten Datenquel-
lentyp verfügbar sind. Die wirkliche Innovation von OLE DB bestand in Microsofts
Strategie des Universaldatenzugangs (UDA): An verschiedenen Orten gespeicherte Da-
ten sollten durch eine einzelne vereinheitlichte Datenzugriffstechnologie verfügbar ge-
macht werden. OLE DB ist die Grundlage der Implementierung dieser Strategie. Noch
bevor die Anzahl der OLE DB Provider kontinuierlich größer wurde, stellte Microsoft
weit reichende Unterstützung für OLE DB sicher, indem ein OLE DB Provider für
ODBC-Treiber zur Verfügung gestellt wurde.

2.5 ADO
ADO (ActiveX Data Objects) ist die Technologie, die ADO.NET seinen Namen gab
(obgleich die Unterschiede weitaus größer als die Ähnlichkeiten sind). ADO ist im
Prinzip lediglich eine „Hülle“ um OLE DB. Es verwendet die bereits bekannten OLE-
DB-Techniken, erleichtert Benutzern von Sprachen wie Visual Basic und Skriptspra-
chen durch ein einfaches Objektmodell aber dennoch den Zugang zu OLE-DB-
Datenquellen. Seine Popularität erlangte es durch die beträchtliche Zahl von Visual-
Basic-, ASP- und Visual-J++-Entwicklern, die dadurch leichten Zugang zu Daten an
unterschiedlichen Orten hatten.

Da es sich bei ADO um ein COM-Interface handelt, muss es gewissermaßen von außen
an ein Projekt „angeflanscht“ werden. ADO.NET ist hingegen vollständig in das .NET-
Framework integriert und somit der Einheitlichkeit zuträglicher. Nichtsdestotrotz stellt
ADO in bestimmten Szenarien auch heute noch eine Wahlmöglichkeit für .NET-
Entwickler dar.

3
.NET und C# · Datenbankzugriff mit ADO.NET 3 · ADO.NET-Architektur

3. ADO.NET-Architektur
3.1 Das Schichtenmodell
Eine Vorstellung von der architektonischen Struktur von ADO.NET gewinnt man leicht
anhand des folgenden Schichtenmodells:

Die Kernfunktionalität von ADO.NET spaltet sich in den datenspeicherspezifischen Teil


der Managed-Provider-Komponenten und in den nicht datenspeicherspezifischen Teil
der datenbezogenen Komponenten).

Auf der untersten Schicht befindet sich der Datenspeicher. Dies kann ein Datenbank-
Server (z.B. SQL Server) oder eine OLE-DB-Datenquelle sein, aber auch jede beliebige
relationale oder nicht-relationale Datenkollektion. Der Datenspeicher ist im Regelfall
unabhängig von .NET.

Die Brücke bildet der Managed Provider, der auf dem Datenspeicher aufsetzt und den
Anschluss an das .NET-Framework schafft. Wie überall in .NET bedeutet die Eigen-
schaft managed auch hier, dass der Provider in der gemeinsamen Laufzeitumgebung
(CLR) läuft. Es handelt sich also um eine Reihe von .NET-Komponenten, auf die später
noch genauer eingegangen wird. Der Managed Provider umfasst die Verbindung zur
Datenquelle, das Ausführen von (SQL-) Kommandos und den Transport der Daten zu
und von der nächstfolgenden Schicht, den datenbezogenen Komponenten (insbeson-
dere Data Set).

4
.NET und C# · Datenbankzugriff mit ADO.NET 3 · ADO.NET-Architektur

Zur obersten Schicht der Daten-Consumer gehören insbesondere datengebundenen


Komponenten der grafischen Benutzeroberfläche (unter anderem DataGrid, TextBox,
ListBox, …), die die Daten – wie der Name bereits suggeriert – beispielsweise zur In-
teraktion mit dem Benutzer verwerten.

Auf die einzelnen Komponenten wird in Kapitel 4 ausführlich eingegangen.

3.2 .NET Managed Data Provider

Ein .NET Data Provider wird für die Erstellung einer Verbindung mit einer Daten-
bank, zur Ausführung von Befehlen und zum Abrufen von Ergebnissen verwendet. Die-
se Ergebnisse werden entweder direkt verarbeitet (DataReader) oder in ein DataSet ge-
stellt, um sie dem Benutzer – eventuell kombiniert mit Daten aus weiteren Quellen – zur
Verfügung zu stellen. Der .NET Data Provider wurde so kompakt entworfen, dass nur
eine minimale Schicht zwischen der Datenquelle und dem Code liegt, wodurch die Leis-
tung ohne Funktionseinbußen erhöht wird.

Ein .NET Data Provider besteht aus den folgenden vier Haupttypen:

· Connection
Stellt eine Verbindung mit einer bestimmten Datenquelle her.
· Command
Führt einen Befehl für eine Datenquelle aus. Stellt Parameter zur Verfügung und kann
innerhalb des Bereichs einer Transaktion aus einer Connection ausgeführt werden.
· DataReader
Liest einen schreibgeschützten Vorwärtsdatenstream aus einer Datenquelle.
· DataAdapter
Füllt ein DataSet und gleicht Änderungen mit der Datenquelle ab.

Das .NET Framework enthält standardmäßig die Data Provider für SQL Server und
OLE DB. Data Provider für ODBC und Oracle sind via optionalem Download ebenfalls

5
.NET und C# · Datenbankzugriff mit ADO.NET 3 · ADO.NET-Architektur

bei Microsoft erhältlich. Ferner können von Drittherstellern Data Provider für weitere
Datenbanksysteme wie beispielsweise PostgreSQL oder MySQL erworben werden.

Die Funktionen der einzelnen Objekte des Data Providers werden in Kapitel 4 näher
beleuchtet.

3.3 ADO.NET-Namespaces
Wie alle anderen Bestandteile des .NET-Universums ist ADO.NET in einigen wenigen
zusammengehörigen Namespaces definiert. Diese befinden sich in einem einzelnen As-
sembly mit dem Namen System.Data.dll. Von allen ADO.NET-Namespaces bildet Sys-
tem.Data den kleinsten gemeinsamen Nenner und muss daher in allen Anwendungen
mit Datenzugriff verwendet werden. Um eine Verbindung mit einem Datenspeicher
herzustellen, ist ferner eine using-Direktive für die System.Data.OleDb- oder Sys-
tem.Data.SqlClient-Namespaces nötig.

Die folgende Auflistung zeigt die wichtigsten ADO.NET-Namespaces und ihre Funkti-
onen im Überblick:

· System.Data
… ist der wichtigste Namespace von ADO.NET. Er definiert Typen für die Darstel-
lung der eigentlichen Daten wie Tabellen, Zeilen, Spalten, Einschränkungen und Da-
taSets, jedoch keine Typen für das Herstellen einer Verbindung mit einer Datenquelle.
· System.Data.Common
… enthält Typen, die von den Managed Data Provider gemeinsam genutzt werden.
Viele dieser Typen fungieren als Basisklassen für die konkreten Typen, die von den
verwalteten OleDb- und SqlClient-Providern definiert werden.
· System.Data.OleDb
… definiert die Typen, mit denen eine Verbindung zu einer OLE-DB-Datenquelle her-
gestellt, SQL-Abfragen übermittelt und Datasets gefüllt werden können.
· System.Data.SqlClient
… definiert die Typen, die den Managed SQL Provider bilden. Mit Hilfe dieser Typen
ist die direkte Kommunikation mit Microsoft SQL Server möglich – ohne Umwege
über das OleDb-Äquivalent.
· System.Data.SqlTypes
Diese Typen stellen die systemspezifischen Datentypen in Microsoft SQL Server dar.
Obwohl jederzeit die entsprechenden CLR-Datentypen verwendet werden können,
sind die SqlTypes-Typen für SQL Server optimiert.

3.4 Verbundene und unverbundene Welt


Unter verbundenem Datenzugriff versteht man Verbindungen einer Applikation zu
Datenquellen, die ständig oder zumindest über längere Zeiträume bestehen, z.B. wäh-
rend der gesamten Laufzeit eines Programms oder während der gesamten Dauer einer
Session. Dazu gehören vor allem Anwendungen, die nur innerhalb eines Firmen-LANs
laufen und bei denen die Anzahl von bestehenden Datenbankverbindungen überschau-
bar ist.

6
.NET und C# · Datenbankzugriff mit ADO.NET 3 · ADO.NET-Architektur

Unter unverbundenem Datenzugriff versteht man hingegen Verbindungen einer Appli-


kation zu Datenquellen, die nur von relativ kurzer Dauer sind. Diese Art von Verbin-
dungen werden hauptsächlich genutzt, wenn die Anwendung über das Internet mit der
Datenquelle verbunden ist oder die Datenquelle sehr stark frequentiert ist. Solche Da-
tenquellen müssen oft mehreren tausend Anwendern gleichzeitig zur Verfügung stehen.
Es ist daher nicht möglich, dass einzelne Anwendungen dauerhafte Verbindungen für
sich beanspruchen.

ADO.NET ist architektonisch vor allem auf den unverbundenen Datenzugriff ausgelegt,
um seiner Ausrichtung insbesondere auf Internetanwendungen Rechnung zu tragen.
Dennoch wird neben dem unverbundenen auch der verbundene Datenzugriff unterstützt.

Für den unverbundenen Datenzugriff stellt ADO.NET als eines seiner Herzstücke das
DataSet zur Verfügung – eine Art Zwischenspeicher für relationale Daten. Für den ver-
bundenen Datenzugriff kommt hingegen der DataReader zum Einsatz. Sowohl auf das
DataSet als auch den DataReader wird im folgenden Kapitel 4 genauestens eingegan-
gen.

7
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

4. ADO.NET-Komponenten
4.1 Connection
Connections sind für das Handling der physikalischen Kommunikation zwischen Da-
tenspeicher und einer .NET-Anwendung verantwortlich. Weil das Connection-Objekt
Teil eines Data Providers ist, implementiert jeder Data Provider eine eigene spezifische
Version. Beispielsweise implementiert der SQL Data Provider SqlConnection im Sys-
tem.Data.SqlClient-Namespace bzw. OLE DB Data Provider OleDbConnection im
System.Data.OleDB-Namespace.

4.1.1 Wichtige Connection-Properties im Überblick

Property Bedeutung Default SQL OLEDB


ConnectionString zur Verbindung mit einer Datenquelle genutz- Leer Ja Ja
ter String (bei Ausführung der Open-Methode)
ConnectionTimeout Maximale Zeit in Sekunden zum Verbindungs- 15 Ja Ja
aufbau, bevor eine Exception ausgelöst wird.
Database Name der Datenbank, die nach Verbindungs- Leer Ja Ja
aufbau geöffnet wird.
DataSource Pfad und Datei, die die Datenbank enthält. Leer Ja Ja
Provider Name des OLE DB Data Providers Leer Nein Ja
ServerVersion Version des verwendeten Servers Leer Ja Ja
State Wert, der den Status der aktuellen Verbindung Closed Ja Ja
anzeigt.
PacketSize Größe der zur Kommunikation mit dem SQL 8192 byte Ja Nein
Server verwendeten Netzwerk-Pakete
WorkStationID String zur Identifikation des Clients oder Name Leer Ja Nein
der Workstation

4.1.2 Connection-Konstruktoren

Methode Bedeutung
New() Erzeugt neues Connection-Objekt, wobei die Property ConnectionString auf
leer gesetzt wird.
New(ConnectionString) Erzeugt neues Connection-Objekt mit spezifizierter Property Connecti-
onString.

4.1.3 Wichtige Connection-Methoden im Überblick

Methode Bedeutung
ChangeDatabase Wechselt bei offener Verbindung die aktuelle Datenbank.
Close Schließt die Verbindung zur Datenquelle.
CreateCommand Erstellt ein mit der Connection assoziiertes DataCommand und gibt es zu-
rück.
Open Etabliert eine Verbindung zur Datenquelle.

Die Open- und Close-Methoden werden automatisch durch die beiden Objekte DataA-
dapter und DataCommand aufgerufen, die jeweils eine Connection verwenden. Falls
erforderlich, kann der Aufruf aber auch explizit im Code erfolgen.

8
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Wenn die Open-Methode einer Connection durch den DataAdapter oder ein DataCom-
mand aufgerufen wird, lassen diese Objekte die Connection in dem Zustand, in dem sie
diese vorfanden. Wenn die Connection beispielsweise zum Zeitpunkt des Aufrufs einer
DataAdapter.Fill-Methode geöffnet war, bleibt sie geöffnet, wenn die Fill-Operation
abgeschlossen ist. Wenn andererseits die Connection geschlossen war, wenn die DataA-
dapter.Fill-Methode aufgerufen wurde, schließt der DataAdapter sie nach Beendigung
der Operation auch wieder. Wenn Sie die Open-Methode explizit aufrufen, bleibt die
Connection geöffnet, bis sie ausdrücklich wieder geschlossen ist.

Obwohl es am einfachsten ist, sich die Open- und Close-Methoden als diskrete Operati-
onen vorzustellen, werden Connections in Wirklichkeit vom .NET-Framework der Per-
formance wegen gepoolt. Die Spezifikationen dieses Poolings werden durch den Data
Provider festgelegt.

4.1.4 Beispiel

Im folgenden Beispiel wird eine Connection zur Northwind-Datenbank aufgebaut und


wieder geschlossen. Im Erfolgsfall ergibt sich folgende Ausgabe:

namespace Beispiele.Connection
{
using System;
using System.Data.SqlClient;

public class TestConnection


{
public static void Main()
{
TestConnection myTestConnection = new TestConnection();
myTestConnection.Run();
}
public void Run()
{
String myConnectionString =
"server=(local)\\NetSDK;Integrated Security=SSPI;database=northwind";
SqlConnection mySqlConnection = new SqlConnection(myConnectionString);
try
{
mySqlConnection.Open();
Console.WriteLine
("Connection zu {0} aufgebaut.", mySqlConnection.ConnectionString);

mySqlConnection.Close();
Console.WriteLine("Connection geschlossen.");
}
catch
{
Console.WriteLine
("Konnte keine Verbindung zu {0} herstellen.",
mySqlConnection.ConnectionString);
}
}
}
}

9
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

4.2 Command
Im Wesentlichen ist ein ADO.NET DataCommand ein SQL-Befehl oder eine Referenz
auf eine Stored Procedure, der bzw. die gegen ein Connection-Objekt ausgeführt wird.
Außer zum Auslesen und zur Aktualisierung von Daten kann DataCommand auch ver-
wendet werden, um bestimmte Arten von Querys auf der Datenquelle auszuführen, die
keine Ergebnismenge zurückgeben, und um Data Definition Commands (DDL) auszu-
führen, die die Struktur der Datenquelle ändern. Wenn ein Command einen Ergebnis-
menge zurückgibt, wird dafür ein DataReader verwendet (siehe dazu den folgenden
Abschnitt 4.3). Als Teil des Data Providers ist Command datenquellenspezifisch. Jeder
Data Provider implementiert Command, beispielsweise OleDbCommand im Sys-
tem.Data.OleDb-Namespace und SqlCommand im System.Data.SqlClient-Namespace.

4.2.1 Wichtige Command-Properties im Überblick

Property Bedeutung
CommandText auszuführendes SQL-Statement oder auszuführende Stored Procedure
CommandTimeout Zeit (ins Sekunden), auf eine Antwort der Datenquelle zu warten.
CommandType Festlegung, wie die CommandText-Property interpretiert wird.
Connection Connection-Objekt, auf dem der DataCommand ausgeführt wird.
Parameters Parameter-Kollektion
Transaction Transaktion, in der das DataCommand ausgeführt wird.
UpdatedRowSource Festlegung, wie Abfrageergebnisse auf eine DataRow angewendet werden,
wenn Command von der Update Methode eines DataAdapters verwendet wird.

Die String-Property CommandText enthält entweder den tatsächlichen Text des Com-
mands, das gegen die Connection ausgeführt werden soll, oder den Namen einer Stored
Procedure in der Datenquelle.

Die Property CommandTimeout determiniert, wie lange Command auf eine Antwort
vom Server wartet, bevor er eine Fehlermeldung generiert. Anmerkung: Dies ist die
Wartezeitzeit, bevor Command anfängt, erste Ergebnisse zu empfangen, und nicht die
Zeit, Command komplett abzuarbeiten. Die Datenquelle könnte beispielsweise zehn
Minuten brauchen, um alle Datensätze zu übertragen. Vorausgesetzt, dass der erste Da-
tensatz innerhalb der spezifizierten Zeit empfangen wird, wird keine Fehlermeldung
erzeugt.

Die Property CommandType erklärt dem Command Objekt, wie es den Inhalt der Ei-
genschaft CommandText zu deuten hat. Die möglichen Werte sind StoredProcedure
(Name der Stored Procedure), TableDirect (Tabellenname) oder Text (SQL-Statement).
Default ist Text. TableDirect wird nur durch OleDbCommand, nicht aber SqlCommand
unterstützt, und ist „SELECT * FROM <Tabellenname>“ gleichwertig, wobei <Tabel-
lenname> in der Property CommandText spezifiziert wird.

Die Property Connection enthält eine Referenz auf das Connection-Objekt, auf dem der
Command durchgeführt wird. Das Connection-Objekt muss dem gleichen Namespace
wie Command angehören, d.h. ein SqlCommand muss eine Referenz auf eine SqlCon-
nection und ein OleDbCommand eine Referenz auf eine OleDbConnection enthalten.

10
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

4.2.2 Command-Konstruktoren

Methode Bedeutung
New() Erzeugt neue Standard-Instanz des DataCommands.
New(CommandText) Erzeugt neuen DataCommand mit spezifizierter
Property CommandText.
New(CommandText, Connection) Erzeugt neuen DataCommand mit spezifizierten
Properties CommandText und Connection.
New(CommandText, Connection, Transaction) Erzeugt neuen DataCommand mit spezifizierten
Properties CommandText, Connection, Transaction.

4.2.3 Wichtige Command-Methoden im Überblick

Methode Bedeutung
Cancel Bricht die Ausführung eines DataCommands ab.
CreateParameter Erzeugt einen neuen Parameter.
ExecuteNonQuery Führt einen Command gegen die Connection aus und gibt die Anzahl der
betroffenen Zeilen zurück.
ExecuteReader Sendet den CommandText zur Connection und erstellt einen DataReader.
ExecuteScalar Führt die Query aus und gibt die erste Spalte der ersten Zeile des Results
zurück.
ExecuteXmlReader Sendet den CommandText zur Connection und erstellt einen XMLReader.
Prepare Erzeugt eine vorbereitete (kompilierte) Version des Commands.
ResetCommandTimeout Setz den Wert der CommandTimeout-Property auf den Standardwert zurück.

Die Methode ExecuteReader wird für SQL-Befehle und Stored Procedures verwendet,
die mehrere Zeilen zurückgeben. Die Methode erzeugt ein DataReader-Objekt, welches
im folgenden Abschnitt 4.3 näher betrachtet wird. Die Methode ExecuteReader kann
ohne Parameter oder mit einem der folgenden Parameter ausgeführt werden, die weitge-
hend selbsterklärend sind:

· CloseConnection
Schließt die assoziierte Connection, wenn der DataReader geschlossen wird.
· KeyInfo
Gibt an, das die Query Spalten- und Primary-Key-Informationen zurückgibt.
· SchemaOnly
Gibt nur das Datenbankschema zurück, ohne Einträge in der Datenquelle zu berühren.
· SequentialAccess
Auf das Result jeder Spalte jeder Zeile wird sequentiell zugegriffen.
· SingleResult
Gibt nur einen einzelnen Wert zurück.
· SingleRow
Gibt nur eine einzelne Zeile zurück.

4.2.4 Beispiel

Im folgenden Beispiel wird eine Connection zur Northwind-Datenbank aufgebaut und


per DataCommand eine INSERT-Query übermittelt. Im Erfolgsfall ergibt sich folgende
Ausgabe:

11
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

namespace Beispiele.Command
{

using System;
using System.Data.SqlClient;

public class TestCommand


{
public static void Main()
{
TestCommand myTestCommand = new TestCommand();
myTestCommand.Run();
}

public void Run()


{

string Message = null;

String myConnectionString =
"server=(local)\\NetSDK;Integrated Security=SSPI;database=northwind";
String mySqlCommandString =
"INSERT INTO Customers (CustomerId, CompanyName, ContactName, ContactTitle,"
+ " Address) Values ('ABC','ABC Company', 'John Smith', 'Owner','One My Way')";
String mySqlCleanupString =
"DELETE FROM Customers WHERE CustomerId = 'ABC'";

SqlConnection mySqlConnection = new SqlConnection(myConnectionString);


SqlCommand mySqlCommand = new SqlCommand(mySqlCommandString, mySqlConnection);
SqlCommand mySqlCleanup = new SqlCommand(mySqlCleanupString, mySqlConnection);

try
{
mySqlConnection.Open();
mySqlCleanup.ExecuteNonQuery();
mySqlCommand.ExecuteNonQuery();
Message = "Neuer Eintrag in Tabelle Northwind.Customers erfolgt.";
}
catch(Exception e)
{
Message= "Konnte Eintrag nicht einfügen: " + e.ToString();
}
finally
{
mySqlConnection.Close();
}

Console.Write(Message);
}
}

4.3 DataReader
Es gibt mehrere Möglichkeiten, eine Abfrage an eine Datenquelle zu übermitteln. Der
DataReader bildet davon die einfachste, schnellste aber weniger flexible Möglichkeit,
Informationen aus einem Datenspeicher abzurufen. Der DataReader stellt einen schreib-
geschützten Vorwärtsdatenstrom dar, der jeweils einen einzigen Datensatz als Ergebnis
eines SQL-Befehls zurückgibt. Der DataReader ist von Nutzen, wenn in kurzer Zeit

12
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

umfangreiche Datenbestände durchlaufen werden müssen und keine Notwendigkeit


besteht, mit einer speicherinternen DataSet-Darstellung zu arbeiten. Wenn beispielswei-
se 10.000 Datensätze aus einer Tabelle abgerufen werden, die in einer Textdatei gespei-
chert werden sollen, wäre es sehr aufwändig, diese Informationen in einem DataSet zu
speichern. Wesentlich bequemer ist es, einen DataReader zu erstellen, der die einzelnen
Datensätze so schnell wie möglich durchläuft. Bei einem DataReader bleibt die Verbin-
dung zur Datenquelle im Gegensatz zu DataSets so lange bestehen, bis die Sitzung ge-
schlossen wird. Da es sich bei DataReader um schreibgeschützte Vorwärtsdatenströme
handelt, besteht keine Möglichkeit der Navigation im Inhalt des DataReaders. Es kön-
nen lediglich die einzelnen Datensätze gelesen und in der Anwendung verwendet wer-
den.

4.3.1 Wichtige DataReader-Properties im Überblick

Property Bedeutung
Depth Verschachtelungstiefe der aktuellen Zeile
FieldCount Anzahl der Spalten in der aktuellen Zeile
IsClosed Gibt an, ob der DataReader geschlossen ist.
Item Wert einer Spalte
RecordsAffected Anzahl der veränderten, eingefügten oder gelöschten Zeilen

4.3.2 Wichtige DataReader-Methoden im Überblick

Methode Bedeutung
Close Schließt den DataReader.
GetType Gibt den Wert der spezifizierten Spalte im spezifizierten Typ zurück.
GetDataTypeName Gibt den Namen des Datenquellentyps zurück.
GetFieldType Gibt den Typ der spezifizierten Spalte zurück.
GetName Gibt den Namen der spezifizierten Spalte zurück.
GetOrdinal Gibt die Position der spezifizierten Spalte zurück.
GetSchemaTable Gibt eine DataTable zurück, die die Struktur des DataReaders beschreibt.
GetValue Gibt den Wert der spezifizierten Spalte im ursprünglichen Typ zurück.
GetValues Gibt alle Spalten der aktuellen Zeile zurück.
IsDbNull Gibt an, ob die spezifizierte Spalte einen Null-Wert enthält.
NextResult Rückt den DataReader zum nächsten Result vor.
Read Rückt den DataReader zur nächsten Zeile vor.

Die Methode Read gibt die nächste Zeile des ResultSets zurück. Wenn der DataReader
geöffnet wird, wird er am Anfang der Datei (vor der ersten Reihe, nicht an der ersten
Reihe) positioniert. Read muss aufgerufen werden, damit die erste Reihe des ResultSets
zurückgegeben wird.

Die Methode NextResult wird verwendet, wenn ein SQL-Befehl oder eine Stored Pro-
cedure mehrere ResultSets zurückgeben. Sie positioniert den DataReader am Anfang
des folgenden ResultSets. Auch hier muss wieder Read aufgerufen werden, damit die
erste Reihe des ResultSets zurückgegeben wird.

Die Methode GetValues gibt alle Spalten in der aktuellen Zeile als Objekt-Array zu-
rück, während die Methode GetValue einen einzelnen Wert als einen der .NET-
Framework-Typen zurückgibt. Wenn jedoch der Datentyp des zurückzugebenden Wer-
tes bekannt ist, ist es effizienter, eine der folgenden GetType-Methoden zu verwenden

13
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

(siehe dazu auch Abschnitt 4.3.4): GetBoolean, GetByte, GetBytes, GetChar, GetChars,
GetFloat, GetGuid, GetDateTime, GetDecimal, GetDouble, GetInt16, GetInt32, Ge-
tInt64, GetString, GetTimeSpan.

4.3.3 Beispiel

Im folgenden Beispiel wird eine Connection zur Northwind-Datenbank aufgebaut und


via DataReader die Tabelle Customers ausgelesen. Im Erfolgsfall ergibt sich folgende
Ausgabe:

namespace Beispiele.DataReader
{

using System;
using System.Data;
using System.Data.SqlClient;

public class TestDataReader


{
public static void Main()
{
TestDataReader myTestDataReader = new TestDataReader();
myTestDataReader.Run();
}

public void Run()


{
SqlDataReader myReader = null;

String myConnectionString =
"server=(local)\\NetSDK;Integrated Security=SSPI;database=northwind";
String mySqlCommandString =
"SELECT * FROM customers";

SqlConnection mySqlConnection = new SqlConnection(myConnectionString);


SqlCommand mySqlCommand = new SqlCommand(mySqlCommandString, mySqlConnection);

try
{
mySqlConnection.Open();
myReader = mySqlCommand.ExecuteReader();

Console.Write("Customer ID ");
Console.WriteLine("Company Name");

14
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

while (myReader.Read())
{
Console.Write(myReader["CustomerID"].ToString() + " ");
Console.WriteLine(myReader["CompanyName"].ToString());
}
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
if (myReader != null)
myReader.Close();

if (mySqlConnection.State == ConnectionState.Open)
mySqlConnection.Close();
}
}
}

4.3.4 Typisierter und untypisierter Zugriff

Beim Datenbankzugriff mittels des DataReaders unterscheidet man zwischen typisier-


tem und untypisiertem Zugriff.

Beim untypisierten Zugriff werden die Werte der einzelnen Felder, die als Object zu-
rückgegeben werden, durch die Verwendung der Methode ToString() als String ausge-
geben. Es bleibt also unberücksichtigt, ob es sich dabei beispielsweise um Integer-, De-
cimal-, DateTime- oder andere Typen handelt.

Beim typisierten Zugriff hingegen wird mittels der so genannten Typed Accessors Me-
thoden (z.B. GetBoolean, GetInt16 oder GetDateTime) ein entsprechender Cast durch-
geführt, um ein Feld beispielsweise als Boolschen Ausdruck, als Integer oder als Date-
Time darzustellen.

4.3.4.1 Beispiel untypisierter Zugriff

while (myReader.Read())
{
Console.WriteLine(myReader[0].ToString());
}

4.3.4.2 Beispiel typisierter Zugriff

while (myReader.Read())
{
Console.WriteLine(myReader.GetBoolean(0));
Console.WriteLine(myReader.GetInt16(1));
Console.WriteLine(myReader.GetDateTime(2));
}

4.4 DataAdapter

15
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Wie auch die Connection- und Command-Objekte ist der DataAdapter Teil des Data
Providers und damit datenquellenspezifisch. Beispielsweise ist der OleDbDataAdapter
im System.Data.OleDb-Namespace und der SqlDataAdapter im System.Data.SqlClient-
Namespace implementiert. Beide DataAdapter erben von System.Data.DbDataAdapter,
der wiederum von System.Data.DataAdapter erbt.

Der DataAdapter stellt verschiedene Methoden und Properties zur Verfügung, um Daten
aus einer Datenquelle auszulesen und in einem DataSet zwischenzuspeichern sowie
Änderungen an den Daten wieder mit der Datenquelle abzugleichen. Er erledigt also
sowohl die Aufgabe, die durch eine Query von der Datenquelle zurückgegebenen Daten
in ein DataSet zu schreiben, als auch das Management, wie veränderte Daten mit der
Datenquelle abgeglichen werden können. Das DataAdapter Objekt verkapselt mehrere
DataCommands und eine Connection, die zum Füllen eines DataSets und zur Aktuali-
sierung der Datenquelle verwendet werden. Die Methode Fill des DataAdapter ruft den
SELECT-Command auf, während die Methode Update die INSERT-, UPDATE- oder
DELETE-Commands für jede veränderte Zeile aufrufen. Eines der wichtigsten Features
des DataAdapter Objekt ist es, dass diese Commands explizit angegeben werden kön-
nen, um die Statements zu spezifizieren, die zur Laufzeit zum Abgleich von Datenände-
rungen verwendet werden. Dies beinhaltet auch den Gebrauch von entsprechenden Sto-
red Procedures. Es ist auch möglich, diese Commands mit Hilfe des CommandBuilder-
Objekts erst zur Laufzeit erzeugen zu lassen (z.B. wenn das SELECT-Statement erst
durch Benutzereingaben zur Laufzeit erstellt wird). Dies erfordert jedoch eine zusätzli-
che Datenbankabfrage, um die dazu erforderlichen Metadaten zu gewinnen, und kann
sich somit negativ auf die Performance auswirken.

4.4.1 Wichtige DataAdapter-Properties im Überblick

Property Bedeutung
AcceptChangesDuringFill Legt fest, ob die Methode AcceptChanges aufgerufen wird, wenn dem
DataSet eine neue Zeile hinzugefügt wird (Default: true).
DeleteCommand Der DataCommand, der zum Löschen von Zeilen in der Datenquelle ver-
wendet wird.
InsertCommand Der DataCommand, der zum Einfügen von Zeilen in die Datenquelle
verwendet wird.
MissingMappingAction Legt das Verhalten fest, wenn eingehende Daten keiner existierenden
Tabelle oder Spalte zugeordnet werden können.
MissingSchemaAction Legt das Verhalten fest, wenn eingehende Daten nicht zum Schema des
existierenden DataSets passen.
SelectCommand Der DataCommand, der zum Auslesen von Zeilen aus der Datenquelle
verwendet wird.
TableMappings Kollektion von DataTableMapping-Objekten, die die Beziehung von
Spalten im DataSet und der Datenquelle festlegen.
UpdateCommand Der DataCommand, der zum Aktualisieren von Zeilen in der Datenquelle
verwendet wird.

Die Property MissingMappingAction legt fest, wie das System reagiert, wenn ein Se-
lectCommand Spalten oder Tabellen zurückgibt, die nicht im DataSet vorhanden sind.
Folgende Werte sind möglich:

· Error
Löst eine SystemException aus.

16
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

· Ignore
Ignoriert Spalten und Tabellen, die nicht im DataSet gefunden werden.
· Passthrough (Default-Wert)
Die im DataSet nicht gefundenen Spalten oder Tabellen werden unter Verwendung
ihres Namens in der Datenquelle dem DataSet hinzugefügt.

Auf ähnliche Weise legt die Property MissingSchemaAction fest, wie das System rea-
giert, wenn eine Spalte im DataSet fehlt. Die Property MissingSchemaAction wird nur
dann aufgerufen, wenn die Property MissingMappingAction auf Passthrough gesetzt ist.
Folgende Werte sind möglich:

· Add (Default-Wert)
Fügt dem DataSet die nötigen Spalten hinzu.
· AddWithKey
Fügt dem DataSet sowohl die nötigen Spalten und Tabellen als auch PrimaryKey
Constraints hinzu.
· Error
Löst eine SystemException aus.
· Ignore
Ignoriert die zusätzlichen Spalten.

4.4.2 Wichtige DataAdapter-Methoden im Überblick

Der DataAdapter stellt zwei wichtige Methoden zur Verfügung: Fill lädt Daten von der
Datenquelle in das DataSet und Update transferiert die Daten in die andere Richtung,
also vom DataSet in die Datenquelle.

Die Methode Fill lädt unter Verwendung des im Property SelectCommand des DataA-
dapters spezifizierten Befehls Daten von einer Datenquelle in eine oder mehr Tabellen
eines DataSets. Folgende Variationen der Methode Fill existieren:

Methode Bedeutung
Fill(DataSet) Erzeugt eine DataTable mit dem Namen Table und füllt diese mit den
Zeilen, die von der Datenquelle zurückgegeben werden.
Fill(DataTable) Füllt die spezifizierte DataTable mit den Zeilen, die von der Datenquel-
le zurückgegeben werden.
Fill(DataSet, tableName) Füllt die mit dem tableName-String benannte DataTable innerhalb des
spezifizierten DataSets mit den Zeilen, die von der Datenquelle zu-
rückgegeben werden.
Fill(DataTable, DataReader) Füllt die spezifizierte DataTable unter Verwendung des spezifizierten
DataReaders.
Fill(DataTable, command, Füllt die spezifizierte DataTable unter Verwendung des spezifizierten
CommandBehavior) command-Strings und des spezifizierten CommandBehavior.
Fill(DataSet, startRecord, Füllt die mit dem tableName-String benannte DataTable innerhalb des
maxRecords, tableName) spezifizierten DataSets mit den Zeilen, die von der Datenquelle zu-
rückgegeben werden. Dabei werden nur maximal maxRecords Zeilen
ab dem nullbasierten startRecord berücksichtigt.
Fill(DataSet, tableName, Füllt die mit dem tableName-String benannte DataTable innerhalb des
DataReader, startRecord, spezifizierten DataSets unter Verwendung des spezifizierten DataRea-
maxRecords) ders. Dabei werden nur maximal maxRecords Zeilen ab dem nullba-
sierten startRecord berücksichtigt.

17
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Fill(DataSet, startRecord, Füllt die mit dem tableName-String benannte DataTable innerhalb des
maxRecords, tableName, spezifizierten DataSets unter Verwendung des spezifizierten command-
command, CommandBeha- Strings und des spezifizierten CommandBehavior mit den Zeilen, die
vior) von der Datenquelle zurückgegeben werden. Dabei werden nur maxi-
mal maxRecords Zeilen ab dem nullbasierten startRecord berücksich-
tigt.
Fill(DataTable, adoObject) Füllt die spezifizierte DataTable mit Zeilen aus dem ADO RecordSet
oder Record Objekt, das in adoObject spezifiziert ist. Nur mit OleDb-
DataAdapter möglich.
Fill(DataSet, adoObject, Füllt die spezifizierte DataTable mit Zeilen aus dem ADO RecordSet
tableName) oder Record Objekt, das in adoObject spezifiziert ist, unter Verwen-
dung der im tableName-String zur Festlegung der TableMappings
spezifizierten DataTable. Nur mit OleDbDataAdapter möglich.

Das DataSet verfügt über keine Erinnerung an die Quelle der in ihm enthaltenen Daten.
Änderungen an den Datensätzen werden nicht automatisch in die Datenquelle zurückge-
schrieben. Dies erfolgt ausschließlich mit Hilfe der Methode Update des DataAdapters.
Diese ruft je nach Bedarf das InsertCommand, das DeleteCommand oder das Update-
Command des DataAdapters für jede geänderte Zeile des DataSets auf. Folgende Varia-
tionen der Methode Update existieren:

Methode Bedeutung
Update(DataSet) Aktualisiert die Datenquelle mit der mit Table benannten DataTable
innerhalb des spezifizierten DataSets.
Update (dataRows) Aktualisiert die Datenquelle mit dem spezifizierten Array of dataRows.
Update (DataTable) Aktualisiert die Datenquelle mit der spezifizierten DataTable.
Update (dataRows, Data- Aktualisiert die Datenquelle unter Verwendung des spezifizierten Da-
TableMapping) taTableMapping mit dem spezifizierten Array of dataRows.
Update (DataSet, source- Aktualisiert die Datenquelle mit der in sourceTable spezifizierten Da-
Table) taTable innerhalb des spezifizierten DataSets.

4.4.3 Beispiel

Vor der Demonstration des DataAdapters in Form eines Beispiels betrachten wir zu-
nächst DataSet und DataTable. Ein gemeinsames Beispiel folgt entsprechend später.

4.5 DataSet
Das DataSet ist der Hauptbestandteil der unverbundenen Architektur von ADO.NET.
Das DataSet wurde explizit für den von der Datenquelle unabhängigen Datenzugriff
entwickelt. Infolgedessen kann es mit mehreren und unterschiedlichen Datenquellen,
XML-Daten oder in der Anwendung gespeicherten Daten verwendet werden. Das Data-
Set enthält eine Kollektion von DataTable-Objekten, die wiederum aus Zeilen und Spal-
ten bestehen, sowie Primärschlüssel-, Fremdschlüssel-, Constraint- und Relationsinfor-
mationen über die Daten in den DataTable-Objekten.

Ferner merkt sich das DataSet alle Veränderungen an den enthaltenen Daten, bis die
Datenquelle aktualisiert wird. DataSets sind vollkommen XML-fähig und enthalten Me-
thoden wie GetXml und WriteXml, die XML-Daten leicht produzieren und verarbeiten
können. In einem XML-Szenario ohne Datenbank ermöglichen diese Methoden sogar
die Verwendung von ADO.NET ohne Beteiligung eines Data Providers.

18
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Das DataSet-Objekt liefert ein konsistentes Programmiermodell, das mit allen gegen-
wärtigen Datenspeichermodellen funktioniert: flach, relational und hierarchisch. Die
Daten innerhalb des DataSets können über einen Satz von Standard-APIs manipuliert
werden, die durch das DataSet und seine Child-Objekte zur Verfügung gestellt werden.

ADO.NET unterstützt zwei unterschiedliche Arten von DataSets: typisierte und untypi-
sierte. Architektonisch ist ein untypisiertes DataSet eine direkte Instanz des Sys-
tem.Data.DataSet-Objekts, während ein typisiertes DataSet eine Instanz eines Objekts
ist, das von System.Data.DataSet erbt.

Ein typisiertes DataSet stellt seine Tabellen und die darin enthaltenen Spalten als Ob-
jekt-Properties zur Verfügung. Dadurch wird die Manipulation des DataSets entschei-
dend erleichtert, da auf die Tabellen und Spalten direkt mit Hilfe ihres Namen referen-
ziert werden kann.

Beispiel: Bei einem gegebenen typisierten DataSet dsOrders, das die DataTable dtOr-
derDetails enthält, kann man auf den Wert der OrderID-Spalte in der ersten Zeile wie
folgt referenzieren: dsOrders.dtOrderDetails(0).OrderID

Bei einem untypisierten DataSet mit der gleichen Struktur müsste man jedoch über die
Tables- und Item-Kollektionen auf die DataTable dtOrderDetails und die OrderID-
Spalte referenzieren: dsOrders.Tables("dtOrderDetails").Rows(0).Item("OrderID")

Ferner hat das typisierten DataSet einen weiteren wichtigen Vorteil: Es erlaubt einen
Typcheck der Datenwerte zur Kompilierzeit. Daher ist in diesem Zusammenhang auch
von stark typisiert die Rede.

Ist beispielsweise OrderTotal numerisch, würde der Compiler eine Fehlermeldung in


der folgenden Zeile ausgeben:
dsOrders.dtOrderDetails.Rows(0).OrderTotal = "Hello World";

Bei Verwendung eines untypisierten DataSets würde die folgende Zeile jedoch ohne
Fehlermeldung kompiliert werden:
dsOrders.Tables("dtOrderDetails").Rows(0).Item("OrderTotal") = "Hello World",

Trotz der Vorteile des typisierten DataSets gibt es Fälle, in denen man ein untypisiertes
DataSet benötigt, beispielsweise wenn die Struktur des DataSets erst zur Laufzeit be-
kannt ist.

4.5.1 Wichtige DataSet-Properties im Überblick

Property Bedeutung
CaseSensitive Legt fest, ob bei Vergleichen Groß- und Kleinschreibung berücksichtigt
wird.
DataSetName Der Name, mit dem im Code auf das DataSet referenziert wird.
DefaultViewManager Definiert die voreingestellte Filter- und Sortierordnung des DataSets.
EnforceConstraints Legt fest, ob Contraint-Regeln bei Änderungen berücksichtigt werden.
ExtendedProperties beliebige Zusatzinformationen

19
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

HasErrors Gibt an, ob irgendwelche Zeilen im DataSet Fehler enthalten.


Locale Die zu verwendenden Lokalinformationen, wenn Zeichenketten vergli-
chen werden.
Namespace der zu verwendende Namespace beim Lesen oder Schreiben eines XML
Dokuments
Prefix Ein XML-Prefix, das als Alias für den Namespace verwendet wird.
Relations Eine Kollektion von DataRelation-Objekten, die die Beziehungen der
Tabellen innerhalb des DataSets zueinander definieren.
Tables Kollektion der DataTables innerhalb des DataSets

4.5.2 DataSet-Konstruktoren

Methode Bedeutung
New() Erzeugt ein untypisiertes DataSet mit dem Standardna-
men NewDataSet.
New(dsName) Erzeugt ein untypisiertes DataSet mit dem in dsName
spezifizierten Namen.
New(SerializationInfo, StreamingContext) Wird nur intern vom .NET-Framework verwendet.

4.5.3 Wichtige DataSet-Methoden im Überblick

Methode Bedeutung
AcceptChanges Übermittelt alle ausstehenden Veränderungen des DataSets an die Datenquelle.
Clear Leert alle DataTables im DataSet.
Clone Kopiert die Struktur des DataSets.
Copy Kopiert die Struktur und den Inhalt des DataSets.
GetChanges Gibt ein DataSet zurück, das nur die geänderten Zeilen in jeder seiner Data-
Tables enthält.
GetXml Gibt eine XML-Repräsentation des DataSets zurück.
GetXmlSchema Gibt eine XSD-Repräsentation des DataSet-Schemas zurück.
HasChanges Gibt an, ob das DataSet Änderungen enthält, die noch nicht an die Datenquelle
übermittelt wurden.
InferXmlSchema Wendet ein spezifiziertes XML Schema auf das DataSet an.
Merge Kombiniert zwei DataSets.
ReadXml Liest ein XML-Schema und Daten in das DataSet ein.
ReadXmlSchema Liest ein XML-Schema in das DataSet ein.
RejectChanges Verwirft alle ausstehenden Veränderungen des DataSets.
Reset Versetzt das DataSet in seinen ursprünglichen Zustand zurück.
WriteXml Schreibt die DatasSet-Struktur als XML-Schema inkl. Daten.
WriteXmlSchema Schreibt die DatasSet-Struktur als XML-Schema.

4.6.5 Die Relations-Kollektion

Während die Tabellenkollektion des DataSets die Struktur der die in einem DataSet
gespeicherten Daten definiert, definiert die Relations-Kollektion die Relationen zwi-
schen den DataTables. Die Relations-Kollektion enthält DataRelation-Objekte, von de-
nen jedes die Relation zwischen zwei Tabellen repräsentiert. Das DataRelation-Objekt
erlaubt es, sich auf einfache Weise zwischen Parent- und Child-Zeilen hin- und herzu-
bewegen – bei gegebenem Parent können alle in Relation stehenden Children gefunden
werden, bei gegebenem Child hingegen kann die Parent-Zeile gefunden werden. Data-
Relation-Objekte stellen durch ihre Properties ChildKeyConstraint und ParentKey-
Constraint auch einen Mechanismus zur Aufrechterhaltung der relationalen Integrität
zur Verfügung. Es ist zu beachten, dass, selbst wenn Constraints in einem DataRelation-

20
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Objekt etabliert sind, diese nur dann durchgesetzt werden, wenn die Property Enforce-
Constraints des DataSets auf true gesetzt ist.

4.6 DataTable
DataSets wurden als Repräsentation relationaler Daten innerhalb des Speichers defi-
niert. DataTables enthalten die tatsächlichen Daten. Sie können als Teil der Tabellen-
kollektion des DataSets bestehen, können aber auch unabhängig erzeugt werden. Die
DataTable hat zwar selbst Properties, dient aber primär als Container für drei Kollektio-
nen: die Column-Kollektion, die die Struktur der Tabelle definiert, die Row-Kollektion,
die die Daten selbst enthält und die Constraints-Kollektion, die in Verbindung mit der
Property PrimaryKey für Datenintegrität sorgt.

4.6.1 DataTable-Konstruktoren

Obgleich DataTables meistens nur als Teil eines DataSets benutzt werden, können sie
auch unabhängig erzeugt werden. Folgende drei Formen des DataTable-Konstruktors
existieren:

Methode Bedeutung
New() Erzeugt eine neue DataTable.
New(TableName) Erzeugt eine neue DataTable mit dem in TableName
spezifizierten Namen.
New(SerializableInfo, StreamingContext) Wird nur intern vom .NET-Framework verwendet.

4.6.2 Wichtige DataTable-Properties im Überblick

Property Bedeutung
CaseSensitive Legt fest, ob bei String-Vergleichen Groß- und Kleinschreibung berücksichtigt
wird.
ChildRelations Eine Kollektion von DataRelation Objekten, die diese DataTable als Parent ha-
ben.
Columns Die Kollektion der DataColumn Objekte innerhalb der DataTable
Constraints Die Kollektion der Constraints innerhalb der DataTable
DataSet Das DataSet, deren Member diese DataTable ist.
DisplayExpression Ein Ausdruck zur Repräsentation dieser DataTable im Benutzerinterface
HasErrors Gibt an, ob irgendwelche Zeilen in der DataTable Fehler enthalten.
ParentRelations Eine Kollektion von DataRelation Objekten, die diese DataTable als Child haben.
PrimaryKey Eine Array von Spalten, die als Primärschlüssel der DataTable fungieren.
Rows Die Kollektion der DataRow Objekte innerhalb der DataTable
TableName Der Name der Tabelle im DataSet. Auf diesen Namen wird im Code referenziert.

Falls die DataTable einem DataSet angehört, wird die Property CaseSensitive stan-
dardmäßig auf den Wert der entsprechenden DataSet-Property CaseSensitive gesetzt,
ansonsten auf false.

Die ChildRelations- und ParentRelations-Kollektionen enthalten Referenzen auf die


DataRelations, die die DataTable als Child bzw. Parent referenzieren. Für die meisten
unabhängigen DataTables sind diese Kollektionen Null, es ist aber theoretisch möglich,
den ChildRelations und ParentRelations eine Relation hinzuzufügen, wenn beispiels-
weise die DataTable zu sich selbst in Relation steht.

21
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

4.6.3 Wichtige DataTable-Methoden im Überblick

Methode Bedeutung
AcceptChanges Übermittelt die ausstehenden Veränderungen aller Zeilen an die Datenquelle.
BeginLoadData Deaktiviert Notifikationen, Indexverwaltung und Constraint-Durchsetzung,
während große Datenmengen eingelesen werden und wird im Zusammenhang
mit den Methoden LoadDataRow und EndLoadData verwendet.
Clear Löscht alle DataRows in der DataTable.
Clone Kopiert die Struktur der DataTable.
Compute Führt Berechnungen (z.B. Summe) über mehrere Zeilen aus.
Copy Kopiert die Struktur und den Inhalt der DataTable.
EndLoadData Reaktiviert Notifikationen, Indexverwaltung und Constraint-Durchsetzung,
nachdem der Einlesevorgang großer Datenmengen abgeschlossen ist.
ImportRow Kopiert eine DataRow inkl. aller Werte und dem RowState in die DataTable.
LoadDataRow Wird während eines Bulk Updates einer DataTable zum Aktualisieren oder
Hinzufügen von DataRows verwendet.
NewRow Erzeugt eine neue DataRow, die dem Schema der DataTable entspricht.
RejectChanges Verwirft alle ausstehenden Veränderungen der DataTable.
Select Gibt einen Array von DataRow-Objekten zurück.

Die Methode Select wird verwendet, um die Zeilen einer DataTable zur Laufzeit zu
filtern und zu sortieren. Sie beeinflusst nicht den Inhalt der DataTable, sondern gibt
stattdessen einen Array von DataRows zurück, die den spezifizierten Kriterien entspre-
chen.

4.6.4 Methoden zum Hinzufügen von DataTables zu einem DataSet

Die folgenden Methoden zum Hinzufügen von DataTables zu einem DataSet werden
von der Tabellenkollektion und nicht vom DataSet selbst aufgerufen, also beispielswei-
se myDataSet.Tables.Add() und nicht myDataSet.Add().

Methode Bedeutung
Tables.Add() Erzeugt eine neue DataTable innerhalb des DataSets mit dem
Namen TableN, wobei N eine fortlaufende Nummer ist.
Tables.Add(TableName) Erzeugt eine neue DataTable innerhalb des DataSets mit dem im
TableName-String spezifizierten Namen.
Tables.Add(DataTable) Fügt dem DataSet die spezifizierte DataTable hinzu.
Tables.AddRange(TableArray) Fügt dem DataSet die im TableArray enthaltenen DataTables
hinzu.

4.6.5 Die Columns-Kollektion

Die Columns-Kollektion der DataTable enthält DataColumn-Objekte, die die Struktur


der Tabelle definieren. Wenn die DataTable durch die DataAdapter Methoden Fill oder
FillSchema kreiert wird, wird die Columns-Kollektion automatisch erzeugt, ansonsten
werden folgende Konstruktoren verwendet:

4.6.5.1 DataColumn-Konstruktoren

Methode Bedeutung
New() Erzeugt eine neue DataColumn ohne Namen.

22
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

New(columnName) Erzeugt eine neue DataColumn mit dem in columnName


spezifizierten Namen.
New(columnName, dataType) Erzeugt eine neue DataColumn mit dem in columnName
spezifizierten Namen sowie dem in dataType spezifizier-
ten Datentyp.
New(columnName, DataType, Expression) Erzeugt eine neue DataColumn mit dem in columnName
spezifizierten Namen sowie dem spezifizierten Datentyp
und der spezifizierten Expression.
New(columnName, DataType, Expression, Erzeugt eine neue DataColumn mit dem in columnName
ColumnMapping) spezifizierten Namen sowie dem spezifizierten Datentyp,
der spezifizierten Expression und dem spezifizierten
ColumnMapping.

4.6.5.2 Wichtige DataColumn-Properties im Überblick

Die primären Properties der DataColumn entsprechen weitgehend den Eigenschaften


der Datenspalten in den meisten relationalen Datenbanken und lauten wie folgt:

Property Bedeutung
AllowDbNull Legt fest, ob die Spalte leer bleiben kann.
AutoIncrement Legt fest, ob das System den Wert der Spalte automatisch erhöht.
AutoIncrementSeed Startwert für eine AutoIncrement-Spalte
AutoIncrementStep Erhöhungsschritte für eine AutoIncrement-Spalte
Caption Spaltenname, wie er in einigen Elementen wie beispielsweise dem DataGrid
angezeigt wird. Standard ist der Wert der Property ColumnName.
ColumnName Spaltenname in der Column-Kollektion. Auf diesen Namen wird im Code refe-
renziert.
DataType .NET-Framework-Datentyp der Spalte
DefaultValue Standardwert der Spalte, falls kein anderer Wert angegeben wird.
Expression Ausdruck, der zu Berechnung des Spaltenwertes verwendet wird.
MaxLength Maximale Länge einer Textspalte
ReadOnly Legt fest, ob der Spaltenwert nach Einfügen der ihn enthaltenden Zeile geändert
werden kann.
Unique Legt fest, ob jede Zeile einen unterschiedlichen Wert in dieser Spalte haben
muss.

Wichtig: Der dezimale Datentyp des .NET-Frameworks und der dezimale Datentyp des
Microsoft SQL Servers sind leider nicht vollständig kompatibel. Der dezimale Datentyp
des .NET-Frameworks erlaubt ein Maximum von 28 signifikanten Stellen, während der
dezimale Datentyp des Microsoft SQL Servers 38 signifikante Stellen erlaubt. Wenn
eine DataColumn als System.Decimal definiert und von einer SQL-Server-Tabelle ge-
füllt wird, lösen alle Zeilen mit mehr als 28 signifikanten Stellen eine Exception aus.

4.6.6 Die Row-Kollektion

Die Row-Kollektion der DataTable enthält in Form von DataRow-Objekten die eigent-
lichen Daten der DataTable.

4.6.6.1 Wichtige DataRow-Properties im Überblick

Property Bedeutung
HasErrors Gibt an, ob die Zeile Fehler enthält.
Item Wert einer Spalte in der Zeile

23
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

ItemArray Wert aller Spalten in der Zeile in Form eines Arrays


RowError Text für die Fehlerbeschreibung
RowState DataRow-Status der Zeile
Table DataTable, zu der die Zeile gehört.

Die Property RowState der DataRow reflektiert die seit der Erzeugung oder dem letzten
Aufruf der Methode AcceptChanges durchgeführten Aktionen. Die möglichen Werte für
RowState lauten:

Werte Bedeutung
Added DataRow ist neu.
Deleted DataRow wurde aus der DataTable gelöscht.
Detached DataRow wurde der DataTable noch nicht hinzugefügt.
Modified Inhalte der DataRow wurden verändert.
Unchanged DataRow wurde nicht modifiziert.

4.6.6.2 Wichtige DataRow-Methoden im Überblick

Methode Bedeutung
AcceptChanges Übermittelt alle ausstehenden Veränderungen der Zeile an die Datenquelle.
BeginEdit Beginnt eine Edit-Operation.
CancelEdit Bricht eine Edit-Operation ab.
Delete Löscht die Zeile.
EndEdit Beendet eine Edit-Operation.
GetChildRows Gibt alle Child-Zeilen einer DataRow zurück.
GetParentRow Gibt die Parent-Zeile einer DataRow auf Basis der spezifizierten DataRelation
zurück.
GetParentRows Gibt die Parent-Zeilen einer DataRow auf Basis der spezifizierten DataRelation
zurück.
HasVersion Gibt an, ob die spezifizierte Version der DataRow existiert.
IsNull Gibt an, ob die spezifizierte Spalte Null ist.
RejectChanges Verwirft alle ausstehenden Veränderungen der DataRow.
SetParentRow Legt die Parent-Zeile einer DataRow fest.

Die Methoden GetChildRows und GetParentRows der DataRow werden verwendet,


um durch die Relationen zu navigieren, die unter Verwendung der Relationen-
Kollektion des DataSets eingerichtet wurden. Beide Methoden sind überladen, so dass
sowohl eine DataRelation selbst als auch ein den Namen einer DataRelation repräsentie-
render String und optional ein RowState-Wert übergeben werden können.

4.6.6.3 Methoden zum Hinzufügen von DataRows zu einer DataTable

Da es sich bei der Property Row der DataTable um eine Kollektion handelt, können
neue Datensätze unter Verwendung der Methode Add eingefügt werden, die in den fol-
genden beiden Varianten verfügbar ist:

Methode Bedeutung
Add(DataRow) Fügt der DataTable die spezifizierte DataRow hinzu.
Add(dataValues()) Erzeugt eine neue DataRow in der DataTable und setzt deren
Item-Werte entsprechend dem in dataValues spezifizierten Ob-
jekt-Array.

24
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

4.6.7 Die Constraints-Kollektion

Zusammen mit der Property PrimaryKey der DataTable sorgt die Constraints-
Kollektion der DataTable für die Aufrechterhaltung der Datenintegrität. Das Sys-
tem.Datra.Constraint-Objekt hat nur die folgenden beiden Properties:

4.6.7.1 Wichtige Constraints-Properties im Überblick

Property Bedeutung
ConstraintName Name des Constraints. Auf diesen Namen wird im Code referenziert.
Table DataTable, zu der das Constraint-Objekt gehört.

Offensichtlich ist ein Objekt mit nur diesen beiden Properties von geringem Nutzen, die
Datenintegrität aufrechtzuerhalten. Daher werden in realen Anwendungen Instanzen der
vom Constraint-Objekt erbenden Objekte ForeignKeyConstraint oder UniqueConstraint
verwendet.

4.6.7.2 Wichtige ForeignKeyConstraint-Properties im Überblick

Das ForeignKeyConstraint repräsentiert die einzuhaltenden Regeln bei einer Child-


Parent-Beziehung zwischen DataTables oder Zeilen in einer DataTable. Das Fo-
reignKeyConstraint-Objekt hat die folgenden Properties:

Property Bedeutung
AcceptRejectRule Legt die Aktion fest, die bei Aufruf der Methode AcceptChanges erfolgt.
Columns Kollektion der Child-Spalten des Constraints
DeleteRule Legt die Aktion fest, die beim Löschen der Zeile erfolgt.
RelatedColumns Kollektion der Parent-Spalten des Constraints
RelatedTable Parent-DataTable des Constraints
Table Überschreibt die Property Contraint.Table mit der Child-DataTable des
Constraints.
UpdateRule Legt die Aktion fest, die beim Updaten der Zeile erfolgt.

Die Aktionen zum Sicherstellen der Datenintegrität werden von den drei Properties Ac-
ceptRejectRule, DeleteRule und UpdateRule festgelegt. Die möglichen Werte der Pro-
perty AcceptRejectRule sind Cascade oder None. Die Properties DeleteRule und Upda-
teRule können einen der folgenden Werte haben:

Werte Bedeutung
Cascade Löschen bzw. Updaten der in Relation stehenden Zeilen
None Keine Auswirkung auf die in Relation stehenden Zeilen
SetDefault Rücksetzung der in Relation stehenden Zeilen auf ihre Default-Werte
SetNull Rücksetzung der in Relation stehenden Zeilen auf Null

4.6.7.3 Wichtige UniqueConstraint-Properties im Überblick

Das UniqueConstraint stellt sicher, dass die Zeilen in der Spalte oder den Spalten, die in
der Property Columns spezifiziert werden, unterschiedliche Werte haben. Das Unique-
Constraint-Objekt hat die folgenden Properties:

Property Bedeutung

25
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Columns Array der durch das Constraint betroffenen Spalten


IsPrimaryKey Gibt an, ob es sich um eine Primärschlüsselconstraint handelt.

4.7 DataView
Der DataView ist ein Objekt, das darauf ausgelegt ist, auf Grundlage der in einem Da-
taSet gespeicherten Daten maßgeschneiderte Ansichten dieser Daten zu erstellen. Es
stellt Methoden und Properties zur Verfügung, die es aufsetzenden Benutzerinterface-
Objekten wie beispielsweise dem DataGrid ermöglichen, sich an ein DataSet zu binden,
und enthält Properties wie AllowEdit und Count, die Benutzerinterface-Objekten sinn-
volles Arbeiten mit den Daten erlauben. Ein DataView wird nur in Verbindung mit ei-
nem DataSet, aber nie mit einem DataReader verwendet. DataViews liefern gefilterte
und sortierte Ansichten einer einzelnen DataTable. Obgleich das DataView damit die
gleiche Kernfunktionalität wie die Select-Methode des DataSets bereitstellen, besitzen
sie eine Reihe von Vorteilen. Da es sich um eigene Objekte handelt, können DataViews
sowohl zur Design- als auch zur Laufzeit erzeugt und konfiguriert werden. Ferner die-
nen Sie anders als der von der Select-Methode des DataSets zurückgegebene Array von
DataRows – welcher zu diesem Zweck zunächst in ein neues DataSet geladen werden
müsste – als direkte Datenquelle für angebundene Benutzerinterface-Objekte wie dem
DataGrid. Für jede gegebene DataTable können beliebig viele verschiedene DataViews
erstellt werden. Standardmäßig stellt jede DataTable bereits mindestens ein DataView
in seiner Property DefaultDataView zur Verfügung. Bei den Zeilen eines DataViews
handelt es sich trotz ihrer Ähnlichkeit zu DataRows um eigene Objekte, den DataRow-
View-Objekten.

Die Eigenschaften DataRowView werden in Tabelle 8-1 gezeigt. Nur die Einzelteilei-
genschaft wird auch durch das DataRow herausgestellt; die anderen Eigenschaften sind
einzigartig.

4.7.1 DataView-Konstruktoren

Methode Bedeutung
New() Erzeugt einen neuen DataView.
New(DataTable) Erzeugt einen neuen DataView und setzt seine Property
DataTable auf die spezifizierte DataTable.

4.7.2 Wichtige DataView-Properties im Überblick

Property Bedeutung
AllowDelete Legt fest, ob Zeilen im DataView gelöscht werden können.
AllowEdit Legt fest, ob Zeilen im DataView editiert werden können.
AllowNew Legt fest, ob Zeilen zum DataView hinzugefügt werden können.
ApplyDefaultSort Legt fest, ob die durch die zugrunde liegende Datenquelle vorgegebene Stan-
dardsortierordnung verwendet werden soll.
Count Anzahl an DataRowViews im DataView
DataViewManager DataViewManager, zu dem dieser DataView gehört.
Item(Index) DataRowView am spezifizierten Index
RowFilter Der zur Filterung der im DataView enthaltenen Zeilen verwendete Ausdruck
RowStateFilter Der zur Filterung der im DataView enthaltenen Zeilen verwendete DataView-
RowState
Sort Der zur Sortierung der im DataView enthaltenen Zeilen verwendete Ausdruck

26
.NET und C# · Datenbankzugriff mit ADO.NET 4 · ADO.NET-Komponenten

Table DataTable, die die Quelle der Zeilen des DataViews ist.

4.7.3 Wichtige DataView-Methoden im Überblick

Methode Bedeutung
AddNew Fügt dem DataView einen neuen DataRowView hinzu.
Delete Fügt einen DataRowView aus dem DataView.
Find Sucht einen oder mehrere DataRowViews, die den/die spezifizierten Primär-
schlüsselwert(e) enthalten.

4.7.4 DataViewManager

Funktionell ähnelt ein DataViewManager einem DataSet. Während ein DataSet als ein
Container für DataTables fungiert, erfüllt ein DataViewManager diese Funktion für
DataViews – ein Container für jede DataTable in einem DataSet. Auf die DataViews
innerhalb des DataViewManager wird durch die DataViewSettings-Kollektion des Da-
taViewManager zugegriffen. Der Einfachheit halber kann man sich vorstellen, dass für
jede in einem DataSet enthaltene DataTable ein DataViewSetting existiert. In Wirklich-
keit wird das DataViewSetting allerdings nicht physikalisch erzeugt, es sei denn es wird
im Code darauf referenziert.

27
.NET und C# · Datenbankzugriff mit ADO.NET 5 · ADO.NET und XML

5. ADO.NET und XML


5.1 Was ist XML?
XML (eXtensible Markup Language) ist ein vom W3C-Konsortium standardisiertes
Textformat für strukturierte Daten. Da XML leicht zu erstellen und zu interpretieren ist,
hat es schnell eine breite Palette von Anwendungen gefunden und revolutioniert damit
bereits seit mehreren Jahren weite Bereiche des Internets. XML verspricht Interoperabi-
lität von Systemen, die bisher nicht oder nur unter großen Schwierigkeiten miteinander
kommunizieren konnten. XML spielt in der Softwareentwicklung als Format für den
Austausch von Daten eine immer wichtigere Rolle – vom Content Management bis zum
E-Commerce. XML wird auch als Format für die Inhalte neuer „Web-fähiger“ Geräte
jenseits des klassischen Computers verwendet.

Wie HTML ist auch XML eine Markup Language, in der Text mithilfe einer Kombina-
tion von Tags und Attributen organisiert wird. In XML werden Tags im Gegensatz zu
HTML nur dazu verwendet, Datenelementen eine Struktur zu verleihen. Da in XML
beliebige Tag-Namen verwendet werden können, obliegt die Interpretation und Bedeu-
tung der Daten allein der jeweiligen Anwendung bzw. dem Entwickler dieser Anwen-
dung. XML-Entwickler sind zwar völlig frei in ihrer Entscheidung darüber, welche
Tags und Hierarchien bestmöglich zu ihren Daten passen, einige Spezifikationen sollten
jedoch eingehalten werden, um zu gewährleisten, dass der XML-Code als wohlgeformt
eingestuft werden kann. Ein wohlgeformter XML-Code hat die folgenden Eigenschaf-
ten:

• Er enthält genau ein Stammelement mit einem eindeutigen Namen, der in keinem
anderen Element innerhalb des Dokuments auftaucht.
• Die Elemente sind korrekt verschachtelt, so dass es keine überlappenden Tags zwi-
schen verschiedenen Elementen gibt.
• Alle Elementtags sind geschlossen.
• Start- und Endtags eines Elements sind in Bezug auf Groß- und Kleinschreibung
konsistent (XML ist casesensitive).
• Alle Elementattribute sind von Anführungszeichen umschlossen (einfache oder dop-
pelte Anführungszeichen).
• Sonderzeichen sind als integrierte Einheiten definiert.

5.2 Die Verwendung von XML in ADO.NET


Die beiden Zitate von Bill Gates „.NET is simply Microsoft’s platform for XML“ und
„XML is the fundamental fabric to tie all these things together“ machen deutlich, wel-
che Rolle Microsoft XML in .NET und insbesondere auch in ADO.NET zukommen
lässt.

XML und einige verwandte Technologien sind Kernphilosophien von ADO.NET. XML
stellt das Schlüsselelement für die stark verbesserte Interoperabilität von ADO.NET
gegenüber ADO dar. Die Interaktion von XML mit ADO.NET und die Integration von
XML in ADO.NET gehen sehr tief.

28
.NET und C# · Datenbankzugriff mit ADO.NET 5 · ADO.NET und XML

ADO.NET bietet mehrere Optionen, Objekte als XML-Dokumente abzuspeichern sowie


diese auch wieder aus XML-Dokumenten herzustellen. Dies gilt insbesondere für das
DataSet-Objekt als einem der Herzstücke von ADO.NET.

Ferner sorgen ADO.NET- und XML-Klassen für eine vereinheitlichte API, die Ent-
wicklern durch eine duale und synchronisierte Schnittstelle zugänglich gemacht wird.
Datenzugriff und -bearbeitung sind damit einerseits unter Verwendung des hierarchi-
schen und knotenbasierten Systems von XML, andererseits mit Hilfe der relationalen
Methode in Form von Tabellen möglich. Es besteht zudem jederzeit die Möglichkeit,
zwischen der DataSet-Repräsentation der Daten und dem XML-Äquivalent hin und her
zu springen.

Wie jedes andere .NET-Objekt wird das DataSet speicherintern in einem binären For-
mat abgelegt. Anders als andere Objekte wird es jedoch immer in einem speziellen
XML-Format, dem so genannten DiffGram übertragen und serialisiert. Sobald es die
Grenzen der Applikation oder die physikalischen Grenzen des Rechners überschreitet,
wird es automatisch in ein DiffGram verwandelt. Am Ziel wir es dann stillschweigend
in ein binäres und sofort nutzbares Objekt zurückverwandelt. Diese Serialisationsfunk-
tionalitäten stehen auch Anwendungen in Form einiger Methoden zur Verfügung, von
denen insbesondere die bereits im Abschnitt über DataSets erwähnten ReadXml und
WriteXml hervorzuheben sind.

5.2.1 Beispiel

Das folgende Beispiel demonstriert das Abspeichern von DataSets in XML-


Dokumenten sowie das nachfolgende Wiederauslesen.
namespace Beispiele.DataSet2XML
{

using System;
using System.Data;
using System.Data.SqlClient;

public class TestDataSet2XML


{

public static void Main()


{
TestDataSet2XML myTestDataSet2XML = new TestDataSet2XML();
myTestDataSet2XML.Run();
}

public void Run()


{

// Erzeuge neue Connection und SqlDataAdapter


String myConnectionString =
"server=(local)\\NetSDK;Integrated Security=SSPI;database=northwind";
String mySqlCommandString =
"SELECT * FROM Region";
SqlConnection myConnection = new SqlConnection(myConnectionString);
SqlDataAdapter mySqlDataAdapter =
new SqlDataAdapter(mySqlCommandString, myConnection);
mySqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

// Erzeuge neues DataSet


DataSet myDataSet1 = new DataSet();

// Füllt das DataSet

29
.NET und C# · Datenbankzugriff mit ADO.NET 5 · ADO.NET und XML

mySqlDataAdapter.Fill(myDataSet1, "Region");

// Speichert den Inhalt des DataSets in XML


myDataSet1.WriteXml("Region.xml");

// Speichert die Struktur des DataSets in XML


myDataSet1.WriteXmlSchema("RegionSchema.xml");

// Erzeuge neues DataSet


DataSet myDataSet2 = new DataSet();

// Lädt die Struktur des DataSets aus XML


myDataSet2.ReadXmlSchema("RegionSchema.xml");

// Lädt den Inhalt des DataSets aus XML


myDataSet2.ReadXml("Region.xml");

30
.NET und C# · Datenbankzugriff mit ADO.NET 6 · GUI-Komponenten

6. GUI-Komponenten mit Datenbankanbindung


Das .NET-Framework stellt extrem leistungsfähige und flexible Mechanismen zur Ver-
fügung, um Daten an (Kontroll-) Elemente der grafischen Benutzeroberfläche zu bin-
den. Obgleich in den meisten Fällen solche Bindungen zu den in Kontrollelementen
(z.B. Listbox) zur Auswahl stehenden Werte, zur Text-Property einer TextBox oder zu
einem DataGrid geknüpft werden, ist es prinzipiell möglich, Daten an irgendwelche
Properties von (Kontroll-) Elementen zu binden. Dies ermöglicht es, beispielsweise die
Hinter- und Vordergrundfarben eines Formulars sowie dessen Schrifteigenschaften an
den Inhalt einer Zeile einer Datenbanktabelle zu binden. Unter Verwendung dieser
Technik ist es Benutzern möglich, die Benutzerschnittstelle einer Anwendung nach ei-
genen Bedürfnissen einzurichten, ohne irgendwelche Änderungen am zugrunde liegen-
den Programmcode vornehmen zu müssen.

Im Folgenden soll lediglich auf das DataGrid als Beispielkomponente der grafischen
Benutzeroberfläche mit Datenbankanbindung eingegangen werden.

6.1 DataGrid
Das DataGrid zeigt Daten in Zeilen und Spalten organisiert an. Im einfachsten Fall ist es
an eine Datenquelle bestehend aus einer einzigen Tabelle ohne Relationen gebunden. In
diesem Fall erscheinen die Daten wie in einem Spreadsheet in simplen Zeilen und Spal-
ten. Wenn das DataGrid an eine Datenquelle mit mehreren in Relation stehenden Tabel-
len gebunden und die Property AllowNavigation auf true gesetzt ist, stehen in jeder Zei-
le Expander zur Verfügung, die die Navigation von einer Parent-Tabelle zu einer Child-
Tabelle ermöglichen. Durch das Anklicken des Knotens wird die die Child-Tabelle an-
gezeigt, das Anklicken des Zurück-Buttons führt zur ursprünglichen Parent-Tabelle zu-
rück. Auf diese Weise zeigt das DataGrid die hierarchischen Relationen zwischen Ta-
bellen an.

Das DataGrid kann als Benutzerschnittstelle für DataSets bzw. DataViews fungieren,
ebenso als Navigationsinstrument für in Relation stehende Tabellen und bietet ferner
reichhaltige Formatierungs- und Bearbeitungselemente. Anzeige und Manipulation von
Daten sind strikt getrennte Funktionen. Folglich bleiben beispielsweise mehrere Da-
taGrids oder anderen Kontrollelemente, die an dieselbe Datenquelle gebunden sind,
stets in synchronem Zustand.

Um korrekt zu arbeiten, muss das DataGrid zur Entwurfszeit mittels der Properties Da-
taSource und DataMember bzw. zur Laufzeit mittels der Methode SetDataBinding an
eine Datenquelle gebunden werden. Zu gültigen Datenquellen zählen unter anderem
DataTables, DataSets, DataViews und DataViewManagers. Wenn die Daten im verbun-
denen Datensatz durch irgendeinen Mechanismus aktualisiert werden, reflektiert das
DataGrid diese Änderungen automatisch. Eine Bearbeitung der Daten ist auch direkt im
DataGrid möglich, falls die Property ReadOnly auf false gesetzt ist.

Das DataGrid bietet darüber hinaus nahezu unendlich viele Formatierungs-, Navigati-
ons-, Editierungs- und andere Funktionalitäten, die an dieser Stelle den Rahmen spren-

31
.NET und C# · Datenbankzugriff mit ADO.NET 6 · GUI-Komponenten

gen würden. Es sei daher für ausführlichere Informationen auf die MSDN Library ver-
wiesen.

6.1.1 Beispiel

Das folgende Beispiel demonstriert, wie die Customers-Tabelle aus der berühmten
Northwind-Datenbank in ein DataGrid geladen wird.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Beispiele.TestGUI
{

public class myForm : System.Windows.Forms.Form


{
private System.ComponentModel.Container components = null;
private System.Windows.Forms.DataGrid myDataGrid;
private System.Data.DataSet myDataSet;
private System.Data.SqlClient.SqlConnection mySqlConnection;
private System.Data.SqlClient.SqlCommand mySqlSelectCommand;
private System.Data.SqlClient.SqlCommand sqlInsertCommand1;
private System.Data.SqlClient.SqlCommand sqlUpdateCommand1;
private System.Data.SqlClient.SqlCommand sqlDeleteCommand1;
private System.Data.SqlClient.SqlDataAdapter mySqlDataAdapter;
private System.Windows.Forms.Button btLoadData;

public myForm()
{
InitializeComponent();
}

/// <summary>
/// Ressourcen nach der Verwendung bereinigen
/// </summary>
protected override void Dispose (bool disposing)

32
.NET und C# · Datenbankzugriff mit ADO.NET 6 · GUI-Komponenten

{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}

/// <summary>
/// Vom Windows Form-Designer erzeugter Code
/// </summary>
private void InitializeComponent()
{
this.myDataGrid = new System.Windows.Forms.DataGrid();
this.myDataSet = new System.Data.DataSet();
this.mySqlConnection = new System.Data.SqlClient.SqlConnection();
this.mySqlSelectCommand = new System.Data.SqlClient.SqlCommand();
this.mySqlDataAdapter = new System.Data.SqlClient.SqlDataAdapter();
this.btLoadData = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.myDataGrid)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.myDataSet)).BeginInit();
this.SuspendLayout();
//
// myDataGrid
//
this.myDataGrid.DataMember = "";
this.myDataGrid.DataSource = this.myDataSet;
this.myDataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText;
this.myDataGrid.Location = new System.Drawing.Point(8, 8);
this.myDataGrid.Name = "myDataGrid";
this.myDataGrid.Size = new System.Drawing.Size(680, 344);
this.myDataGrid.TabIndex = 0;
//
// myDataSet
//
this.myDataSet.DataSetName = "myDataSet";
this.myDataSet.Locale = new System.Globalization.CultureInfo("de-DE");
//
// mySqlConnection
//
this.mySqlConnection.ConnectionString =
"server=(local)\\NetSDK;Integrated Security=SSPI;database=northwind";
//
// mySqlSelectCommand
//
this.mySqlSelectCommand.CommandText = "SELECT * FROM Customers";
this.mySqlSelectCommand.Connection = this.mySqlConnection;
//
// mySqlDataAdapter
//
this.mySqlDataAdapter.DeleteCommand = this.sqlDeleteCommand1;
this.mySqlDataAdapter.InsertCommand = this.sqlInsertCommand1;
this.mySqlDataAdapter.MissingSchemaAction =
System.Data.MissingSchemaAction.AddWithKey;
this.mySqlDataAdapter.SelectCommand = this.mySqlSelectCommand;
this.mySqlDataAdapter.UpdateCommand = this.sqlUpdateCommand1;
//
// btLoadData
//
this.btLoadData.Location = new System.Drawing.Point(8, 360);
this.btLoadData.Name = "btLoadData";
this.btLoadData.TabIndex = 1;
this.btLoadData.Text = "Daten laden";
this.btLoadData.Click += new System.EventHandler(this.btLoadData_Click1);
//
// myForm
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(696, 389);
this.Controls.Add(this.btLoadData);
this.Controls.Add(this.myDataGrid);

33
.NET und C# · Datenbankzugriff mit ADO.NET 6 · GUI-Komponenten

this.Name = "myForm";
this.Text = "Northwind Customers";
((System.ComponentModel.ISupportInitialize)(this.myDataGrid)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.myDataSet)).EndInit();
this.ResumeLayout(false);
}

/// <summary>
/// Der Haupteinsprungspunkt für die Anwendung
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new myForm());
}

/// <summary>
/// Eventhandler für Klick auf den Daten laden Button.
/// </summary>
private void btLoadData_Click1(object sender, System.EventArgs e)
{
// Das DataSet wird gefüllt...
this.mySqlDataAdapter.Fill(this.myDataSet, "Customers");

// ... und als Datenquelle an das DataGrid gebunden.


this.myDataGrid.DataSource = this.myDataSet.Tables["Customers"].DefaultView;
}

}
}

34

Das könnte Ihnen auch gefallen