24 ADO.NET – Das Command-Objekt
Die Grundlage einer Datenbankabfrage ist die Verbindung zu der Datenquelle. Wie Sie ein SqlConnection-Objekt dazu erzeugen, habe ich im letzten Kapitel gezeigt. Nun gehen wir den nächsten Schritt und wollen uns damit beschäftigen, wie Daten aus der Datenbank abgerufen werden. Damit wird auch in einem Zug erklärt, wie Daten in der Originaldatenbank verändert, hinzugefügt und gelöscht werden. Für solche Operationen stellt ADO.NET eine weitere Klasse zur Verfügung, die je nach eingesetztem Datenprovider SqlCommand, OleDbCommand oder OdbcCommand heißt. Command-Objekte gehören zur Gruppe derjenigen Objekte, die auf die Verbindung zum Datenbankserver angewiesen sind.
Neben der Klasse SqlCommand werden Sie weitere wichtige Klassen kennenlernen, allen voran die Klasse SqlDataReader, die die Datensätze einer Ergebnisliste durchläuft oder Schema-Informationen einer Tabelle abruft. SqlDataReader ist tatsächlich in der gesamten ADO.NET-Klassenbibliothek das einzige Objekt, das Dateninformationen abrufen kann. Auch wenn wir uns später mit der Klasse SqlDataAdapter beschäftigen, die über die Methode Fill ein DataSet zu füllen vermag, hält der DataReader im Hintergrund die Fäden in der Hand. Von außen betrachtet, können wir das allerdings nicht direkt erkennen.
24.1 Das »SqlCommand«-Objekt 

Das SqlCommand-Objekt repräsentiert einen SQL-Befehl oder eine gespeicherte Prozedur. In der Eigenschaft CommandText wird die SQL-Anweisung bzw. die gespeicherte Prozedur festgelegt. Die Ausführung wird mit einer der Execute-Methoden gestartet.
Als kleiner Vorgeschmack soll das folgende Beispiel dienen. Darin wird die Verbindung zu der Datenbank Northwind des SQL Servers aufgebaut. In der Tabelle Products, in der alle Artikel geführt sind, ist u. a. ein Artikel mit der Bezeichnung Chai (Spalte ProductName) vorhanden. Angenommen, dieser sei falsch und soll nun in Sojasauce geändert werden. Dazu übergeben wir der Eigenschaft CommandText des SqlCommand-Objekts ein entsprechendes UPDATE-Kommando und führen es mit ExecuteNonQuery aus. Der Rückgabewert der Methode ist vom Typ int und gibt an, wie viele Datensätze von der Änderung betroffen sind.
// ------------------------------------------------------------------ // Beispiel: ...\Kapitel 24\ExecuteNonQuerySample // ------------------------------------------------------------------ SqlConnection con = new SqlConnection("..."); SqlCommand cmd = new SqlCommand(); cmd.CommandText = "UPDATE Products SET ProductName='Sojasauce' WHERE ProductName='Chai'"; cmd.Connection = con; con.Open(); if( cmd.ExecuteNonQuery() > 0) Console.WriteLine("Erfolgreich aktualisiert!") ; con.Close();
Die genaue Angabe der Verbindungszeichenfolge ist hier ausgelassen – so wie in den meisten folgenden Beispielen auch. Sie können diese dem zweiten Kapitel entnehmen und, falls notwendig, entsprechend Ihrer eigenen lokalen Installation anpassen.
Vom Erfolg der Operation können Sie sich auf verschiedene Weisen überzeugen. Sie können sich einerseits mit dem Tool SQL Server Management Studio von SQL Server 2008 den Inhalt der nun geänderten Tabelle anzeigen lassen. Sie können das aber auch aus dem Server-Explorer von Visual Studio 2010 heraus, den Sie über das Menü Ansicht öffnen. Fügen Sie über das Kontextmenü des Knotens Datenverbindungen die Verbindung zu der Datenbank Northwind hinzu. Ein Assistent, den wir uns später in diesem Buch noch genauer ansehen werden, begleitet Sie durch den gesamten Prozess, an dessen Ende Sie die Möglichkeit haben, sich den aktuellen Inhalt der Tabelle Products in Visual Studio 2010 anzeigen zu lassen.
24.1.1 Erzeugen eines »SqlCommand«-Objekts 

Um ein Kommando gegen eine Datenbank abzusetzen, wird ein SqlCommand-Objekt benötigt. Es spielt dabei keine Rolle, ob es sich um eine Auswahlabfrage (SELECT) oder Aktionsabfrage (INSERT, UPDATE oder DELETE) handelt. Das SQL-Kommando wird der Eigenschaft CommandText des SqlCommand-Objekts zugewiesen. Das ist aber noch nicht ausreichend, denn zusätzlich zum Befehl muss das SqlCommand-Objekt auch den Datenbankserver und die Datenbank kennen. Das heißt nichts anderes, als dass das SqlCommand-Objekt wissen muss, welches SqlConnection-Objekt die Verbindung zur Datenbank beschreibt.
Um diese Anforderungen zu erfüllen, stehen Ihnen mehrere Konstruktoren zur Verfügung. Sie können, wie im Beispiel zuvor gezeigt, den parameterlosen Konstruktor bemühen, müssen dann aber der Eigenschaft SqlConnection des SqlCommand-Objekts die Referenz auf SqlConnection mitteilen. Einer anderen Konstruktorüberladung können Sie neben dem abzusetzenden Kommando auch die Referenz auf das SqlConnection-Objekt übergeben.
SqlCommand cmd = new SqlCommand("UPDATE Products " + "SET ProductName='Sojasauce' " + "WHERE ProductName='Chai'", con);
24.1.2 Die Methode »CreateCommand« des »Connection«-Objekts 

Es gibt noch eine zweite Variante, eine Referenz auf ein SqlCommand-Objekt zu erhalten. Dazu wird die Methode CreateCommand auf dem SqlConnection-Objekt aufgerufen, die als Rückgabewert das providerspezifische SqlCommand-Objekt liefert.
SqlConnection con = new SqlConnection("..."); SqlCommand cmd = con.CreateCommand();
24.1.3 Ausführen des »SqlCommand«-Objekts 

Die CommandText-Eigenschaft legt das Kommando fest, das ausgeführt werden soll. Es kann sich dabei um ein SQL-Kommando oder eine gespeicherte Prozedur handeln. Bei den SQL-Kommandos werden zwei Kategorien unterschieden:
- Auswahlabfragen
- Aktionsabfragen
Eine Auswahlabfrage basiert auf dem SELECT-Statement und liefert ein Ergebnis zurück. Dazu gehören auch die Abfragen, die eine Aggregatfunktion wie SUM oder COUNT aufrufen und nur einen Ergebniswert liefern. Eine typische Auswahlabfrage wäre zum Beispiel:
SELECT ProductName, UnitPrice FROM Products WHERE UnitPrice < 100
Das Resultat dieser Abfrage bilden alle Datensätze der Datenbank Northwind, die diejenigen Produkte beschreiben, deren Preis kleiner 100 ist.
Eine Aktionsabfrage manipuliert die Datenbank. Dabei kann es sich um Folgendes handeln:
- die Aktualisierung der Daten (DML-Abfrage – Data Manipulation Language-Abfrage) oder
- die Änderung der Datenbankstruktur (DDL-Abfrage – Data Definition Language-Abfrage)
Mit
UPDATE Products SET ProductName='Sojasauce' WHERE ProductName='Chai'
hatten wir eingangs eine DML-Abfrage abgesetzt, die zwar einen Datensatz in Products änderte, selbst aber keine Ergebnismenge lieferte.
Wie Sie sehen, führt das Absetzen eines Befehls zu ganz unterschiedlichen Reaktionen des Datenbankservers. Das SqlCommand-Objekt trägt dem Rechnung und stellt mit
- ExecuteNonQuery,
- ExecuteReader,
- ExecuteScalar und
- ExecuteXmlReader
vier Methoden zur Verfügung, die speziell auf die einzelnen Abfragen abgestimmt sind und synchron ausgeführt werden. Synchron bedeutet, dass die Clientanwendung nach dem Methodenaufruf so lange wartet, bis das Ergebnis der Frage vom Datenbankserver eintrifft. Gegebenenfalls kann das eine längere Zeitspanne beanspruchen. Daher wurde mit der Einführung von ADO.NET 2.0 auch die Möglichkeit eingeräumt, Datenbankabfragen asynchron auszuführen. Der Client muss dann nicht warten, bis die Abfrageausführung beendet ist, sondern kann weiterarbeiten, bis ihm signalisiert wird, dass die Ergebnisse vollständig vorliegen.
24.1.4 Die Eigenschaft »CommandTimeout« des »SqlCommand«-Objekts 

Wird eine Abfrage mit einer der vier Execute-Methoden ausgeführt, wartet das SqlCommand-Objekt per Vorgabe 30 Sekunden auf das Eintreffen der ersten Abfrageergebnisse. Das Überschreiten dieser Zeitspanne hat eine Ausnahme zur Folge.
Mittels der Eigenschaft CommandTimeout kann die Voreinstellung verändert werden. Mit der Einstellung 0 wartet das SqlCommand-Objekt eine unbegrenzte Zeit. Empfehlenswert ist das allerdings nicht. Eine Abfrage könnte durchaus so lange andauern, dass die voreingestellte Zeit überschritten wird. Das hat keine weiteren Auswirkungen, weil eine laufende Abfrage nicht unterbrochen wird.