26 Die Datenbankabfrage
Grundlage einer Datenbankabfrage ist die Verbindung zu der Datenquelle. Wie Sie das SqlConnection-Objekt dazu erzeugen, hat das letzte 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 Schemainformationen 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. Am Ende des ADO.NET-Teils dieses Buches werden Sie noch einmal dem SqlCommand-Objekt begegnen, wenn wir abweichend von der Standardvorgabe unsere eigene Aktualisierungslogik codieren.
26.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 kleinen Vorgeschmack möchte ich Ihnen ein Beispiel zeigen. Darin wird die Verbindung zu der Beispieldatenbank Northwind des SQL Servers aufgebaut. In der Tabelle Products, in der alle Artikel geführt sind, ist unter anderem ein Artikel mit der Bezeichnung Chai (Spalte ProductName) gespeichert. 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.
// ---------------------------------------------------// Beispiel: ...\Kapitel 26\ExecuteNonQueryDemo
// -------------------------------------------------------
class Program { static void Main(string[] args) { SqlConnection con = new SqlConnection("..."); SqlCommand cmd = new SqlCommand(); cmd.CommandText = "UPDATE Products " + "SET ProductName='Sojasauce' " + "WHERE ProductName='Chai'"; cmd.Connection = con; con.Open(); cmd.ExecuteNonQuery(); con.Close(); } }
Vom Erfolg der Operation können Sie sich auf verschiedene Weisen überzeugen. Sie könnten sich einerseits mit dem Tool SQL Server Management Studio von SQL Server 2005 den Inhalt der nun geänderten Tabelle anzeigen lassen. Sie können das aber auch aus dem Server-Explorer des Visual Studio 2008 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 noch genauer ansehen werden, führt Sie durch den gesamten Verbindungsprozess, an dessen Ende Sie die Möglichkeit haben, sich den aktuellen Inhalt der Tabelle Products in Visual Studio 2005 anzeigen zu lassen.
26.1.1 Ein »SqlCommand«-Objekt erzeugen 

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 der Referenz auf das SqlConnection-Objekt auch das abzusetzende Kommando als Zeichenfolge übergeben.
SqlCommand cmd = new SqlCommand("UPDATE Products " +
"SET ProductName='Sojasauce' WHERE ProductName='Chai'");Die Methode »CreateCommand« des Connection-Objekts
Es gibt noch eine zweite Variante, sich eine Referenz auf ein SqlCommand-Objekt zu besorgen. Dazu wird die Methode CreateCommand auf dem Sqlonnection-Objekt aufgerufen, die als Rückgabewert das provider-spezifische SqlCommand-Objekt liefert.
SqlConnection con = new SqlConnection("...");
SqlCommand cmd = con.CreateCommand();26.1.2 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 immer 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 Tabelle Northwind, die diejenigen Produkte beschreiben, deren Preis kleiner 100 ist.
Eine Aktionsabfrage manipuliert die Datenbank. Dabei kann es sich
- um die Aktualisierung der Daten (DML-Abfrage = Data Manipulation Language-Abfrage) oder
- um die Änderung der Datenbankstruktur (DDL-Abfrage = Data Definition Language-Abfrage)
handeln. 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
- 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. Sie können aber Datenbankabfragen auch asynchron ausfü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. Entsprechende asynchrone Methoden werden vom Command-Objekt bereitgestellt.
26.1.3 Die Eigenschaft »CommandTimeout« des »SqlCommand«-Objekts 

Wird eine Abfrage mit ExcuteScalar, EcexuteNonQuery oder ExcuteReader ausgeführt, wartet das SqlCommand-Objekt per Vorgabe 30 Sekunden auf das Eintreffen der ersten Abfrageergebnisse. Das Überschreiten der eingestellten Zeit hat zur Folge, dass eine Ausnahme ausgelöst wird.
Mit 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, die gerade ausgeführt wird, könnte durchaus so lange andauern, dass die voreingestellte Zeit überschritten wird. In diesem Fall hat das keine weiteren Auswirkungen, weil eine laufende Abfrage nicht unterbrochen wird.
26.1.4 Aktionsabfragen absetzen 

Abfragen, die Änderungen an den Originaldaten der Datenbank nach sich ziehen (UPDATE, DELETE, INSERT) oder die Struktur einer Datenbank verändern (z. B. CREATE TABLE), werden mit der Methode ExecuteNonQuery abgesetzt.
public int ExecuteNonQuery();Handelt es sich bei dem Befehl um ein UPDATE-, INSERT- oder DELETE-Kommando, können Sie über den Rückgabewert die Anzahl der von der Anweisung betroffenen Datenzeilen feststellen.
Datensätze hinzufügen
Im folgenden Beispielprogramm wird der Tabelle Products ein Datensatz hinzugefügt. Dabei wird der parametrisierte Konstruktor der Klasse SqlCommand verwendet, der im ersten Parameter den SQL-Befehl und im zweiten die Referenz auf das SqlConnection-Objekt entgegennimmt.
// ---------------------------------------------------// Beispiel: ...\Kapitel 26\DatensatzHinzufügen
// -------------------------------------------------------
class Program { static void Main(string[] args) { SqlConnection con = new SqlConnection("..."); // SQL-Befehl string strSQL = "INSERT INTO Products(ProductName, Discontinued) " + "VALUES('Schweizer Käse',0)"; try { con.Open(); SqlCommand cmd = new SqlCommand(strSQL, con); // Kommando absetzen cmd.ExecuteNonQuery(); } catch (Exception e) { Console.WriteLine("Fehlermeldung: {0}", e.Message); } con.Close(); } }
Datensätze löschen
Der Datensatz aus dem vorhergehenden Beispiel soll nun wieder gelöscht werden. Da wir nun daran interessiert sind, ob und wie viele Datenzeilen von einer Löschanweisung betroffen sind, werten wir den Rückgabewert der Methode ExecuteNonQuery an der Konsole aus.
// ---------------------------------------------------// Beispiel: ...\Kapitel 26\DatensatzLöschen
// -------------------------------------------------------
class Program { static void Main(string[] args) { SqlConnection con = new SqlConnection("..."); try { con.Open(); string strSQL = "DELETE FROM Products " + "WHERE ProductName='Schweizer Käse'"; SqlCommand cmd = new SqlCommand(strSQL, con); Console.Write("Anzahl der gelöschten Datensätze = "); Console.WriteLine(cmd.ExecuteNonQuery()); } catch (Exception e) { Console.WriteLine("Fehlermeldung: {0}", e.Message); } con.Close(); Console.ReadLine(); } }
Nach dem ersten Start des Programms wird der im Abschnitt zuvor hinzugefügte Datensatz gelöscht. An der Konsole sehen wir das bestätigt, da die Zahl 1 ausgegeben wird. Rufen wir das Programm ein zweites Mal auf, wird kein Datensatz gefunden, der dem Kriterium ProductName='Schweizer Käse' entspricht. Das spiegelt sich in der Ausgabe
Die Anzahl der gelöschten Datensätze = 0
wider.
Datensätze ändern
Zu Beginn dieses Abschnitts wurde in dem Beispiel ExecuteNonQueryDemo bereits gezeigt, wie Sie Datensätze in der Datenbank editieren können. Daher soll an dieser Stelle auf ein weiteres Beispiel verzichtet werden.
26.1.5 Abfragen, die genau ein Ergebnis liefern 

Mit der SELECT-Anweisung können Sie eine Datensatzliste nach bestimmten Auswahlkriterien aus einer Datenbank abzurufen. Der Befehl SELECT wird aber auch dann benutzt, wenn eine Aggregatfunktion definiert werden soll. Aggregatfunktionen liefern ein Ergebnis zurück. Beispielsweise können Sie mit
SELECT COUNT(*) FROM Northwind
die Anzahl der Artikel in der Tabelle Products ermitteln und mit
SELECT COUNT(*) FROM Products WHERE CategoryID = 1
feststellen, wie viele Artikel zur Kategorie 1 gehören. Neben COUNT stehen noch weitere Aggregatfunktionen zur Verfügung: SUM, um die Summe eines numerischen Ausdrucks zu ermitteln, AVG, um einen Durchschnittswert zu bilden, sowie MIN und MAX, um aus einem gegebenen Ausdruck den Maximal- bzw. Minimalwert zu erhalten.
Um den Rückgabewert einer Aggregatfunktion entgegenzunehmen, rufen Sie die Methode ExecuteScalar auf das SqlCommand-Objekt auf. Der Typ der Rückgabe ist Object, daher muss das Ergebnis noch in den passenden Datentyp konvertiert werden.
string strSQL = "SELECT COUNT(*) FROM Products WHERE CategoryID=1"; SqlCommand cmd = new SqlCommand(strSQL, con); int anzahlDS = Convert.ToInt32(cmd.ExecuteScalar());




Jetzt bestellen





