In diesem Kapitel wird gezeigt, wie eine Datenbank verändert und ausgelesen wird. Weitere Themen sind das Auslesen der Datenbankstruktur und gespeicherte Prozeduren auf dem Datenbankserver.
24 Datenbankabfragen mit ADO.NET
Voraussetzung jeder Datenbankabfrage ist die Verbindung zu der Datenquelle. Wie Sie das Connection-Objekt dazu erzeugen, habe ich im letzten Kapitel gezeigt. Nun gehen wir den nächsten Schritt und widmen uns dem Abruf von Daten aus der Datenbank. Ich erkläre auch, 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 sind auf die Verbindung zum Datenbankserver angewiesen. Der folgende Ausschnitt aus der Vererbungshierarchie zeigt, dass Sie durch eine Beschränkung auf die Funktionen der Klassen DbCommand und DbCommandBuilder leicht zu einem anderen Datenbankprovider wechseln können.
System.Object +System.MarshalByRefObject +System.ComponentModel.Component +System.Data.Common.DbCommand | +System.Data.Odbc.OdbcCommand | +System.Data.OleDb.OleDbCommand | +System.Data.OracleClient.OracleCommand | +System.Data.SqlClient.SqlCommand | +System.Data.SqlServerCe.SqlCeCommand +System.Data.Common.DbCommandBuilder +System.Data.Odbc.OdbcCommandBuilder +System.Data.OleDb.OleDbCommandBuilder +System.Data.OracleClient.OracleCommandBuilder +System.Data.SqlClient.SqlCommandBuilder +System.Data.SqlServerCe.SqlCeCommandBuilder
Neben der Klasse DbCommand werden Sie weitere wichtige Klassen kennenlernen, allen voran die Klasse DbDataReader, die die Datensätze einer Ergebnisliste durchläuft oder Schemainformationen einer Tabelle abruft. DbDataReader ist tatsächlich in der gesamten ADO.NET-Klassenbibliothek die einzige Klasse, die Dateninformationen abrufen kann. Auch wenn wir uns später mit der Klasse DbDataAdapter beschäftigen, die über die Methode Fill ein DataSet zu füllen vermag, hält der DbDataReader 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 DbCommand-Objekt begegnen, wenn wir abweichend von der Standardvorgabe unsere eigene Aktualisierungslogik codieren. Auch bei diesen anderen Klassen lohnt sich eine Beschränkung auf Klassen des Namensraums System.Data. Common, um einen Wechsel des Datenbankproviders zu erleichtern.
System.Object +System.MarshalByRefObject +System.ComponentModel.Component | +System.Data.Common.DataAdapter | +System.Data.Common.DbDataAdapter | +Microsoft.AnalysisServices.AdomdClient.AdomdDataAdapter | +System.Data.Odbc.OdbcDataAdapter | +System.Data.OleDb.OleDbDataAdapter | +System.Data.OracleClient.OracleDataAdapter | +System.Data.SqlClient.SqlDataAdapter | +System.Data.SqlServerCe.SqlCeDataAdapter +System.Data.Common.DbDataReader +System.Data.DataTableReader +System.Data.Odbc.OdbcDataReader +System.Data.OleDb.OleDbDataReader +3System.Data.OracleClient.OracleDataReader +System.Data.SqlClient.SqlDataReader +System.Data.SqlServerCe.SqlCeDataReader +System.Data.SqlServerCe.SqlCeResultSet
Hinweis |
Wenn möglich werden die Db- statt der Sql-Typen verwendet. Alle Aussagen, die sich im Folgenden auf die Db-Typen beziehen, gelten auch für die analogen Sql-Typen. |
24.1 DbCommand
Das DbCommand-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. Die Klasse DbCommand selbst ist abstrakt, und die Objekterzeugung erfolgt über eine providerspezifische Klasse, zum Beispiel SqlCommand.
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. Nehmen wir an, dieser sei falsch und soll nun in Tee geändert werden. Dazu übergeben wir der Eigenschaft CommandText des DbCommand-Objekts ein entsprechendes UPDATE-Kommando und führen es mit ExecuteNonQuery aus.
'...\ADO\Datenbankabfragen\Command.vb |
Option Strict On Imports System.Data.Common Imports System.Data.SqlClient Namespace ADO Module Command Sub Test() Dim con As DbConnection = New SqlConnection() con.ConnectionString = "Data Source=(local);" & _ "Initial Catalog=Northwind;Integrated Security=sspi" Dim cmd As DbCommand = New SqlCommand() cmd.CommandText = "UPDATE Products " & _ "SET ProductName='Tee' WHERE ProductName='Chai'" cmd.Connection = con con.Open() cmd.ExecuteNonQuery() con.Close() Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Wenn Sie in UPDATE die WHERE-Klausel vergessen, werden alle Zeilen geändert. |
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 den Inhalt der nun geänderten Tabelle anzeigen lassen. Sie können das aber auch aus dem Server-Explorer des Visual Studio 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 anzeigen zu lassen.
24.1.1 Ein DbCommand-Objekt erzeugen
Um ein Kommando gegen eine Datenbank abzusetzen, wird ein DbCommand-Objekt benötigt. Dies trifft für eine Auswahlabfrage (SELECT) ebenso zu wie für eine Aktionsabfrage (INSERT, UPDATE oder DELETE). Das SQL-Kommando wird der Eigenschaft CommandText des DbCommand-Objekts zugewiesen. Zusätzlich zum Befehl muss das DbCommand-Objekt auch den Datenbankserver und die Datenbank kennen, das heißt, es muss wissen, welches DbConnection-Objekt die Verbindung zur Datenbank beschreibt.
Um diese Anforderungen zu erfüllen, stehen Ihnen mehrere Konstruktoren zur Verfügung. Verwenden Sie den parameterlosen Konstruktor, müssen Sie der Eigenschaft Connection des DbCommand-Objekts die Referenz auf ein DbConnection-Objekt zuweisen. Einer anderen Konstruktorüberladung können Sie neben der Referenz auf das SqlConnection-Objekt auch das abzusetzende Kommando als Zeichenfolge übergeben.
Dim cmd As DbCommand = New SqlCommand("UPDATE Products " & _ "SET ProductName='Tee' WHERE ProductName='Chai'")
Eine zweite Variante zur Erzeugung eines DbCommand-Objekts ruft die Methode CreateCommand eines DbConnection-Objekts auf.
Dim con As DbConnection = New SqlConnection("...") Dim cmd As DbCommand = con.CreateCommand()
Hinweis |
Wenn Sie eine provider-spezifische Verbindung verwenden, ist das Command-Objekt auch provider-spezifisch. |
24.1.2 Ausführen des DbCommand-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 verwendet die SELECT-Klausel und liefert immer ein Ergebnis zurück, im Fall einer Aggregatfunktion wie SUM oder COUNT liefert sie aber nur einen Ergebniswert. 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='Tee' WHERE ProductName='Chai'
haben wir eingangs eine DML-Abfrage abgesetzt, die zwar einen Datensatz in Products ändert, aber die geänderten Daten nicht zurückgibt, sondern nur die Anzahl Änderungen.
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. Die ersten drei sind auch in DbCommand enthalten. Alle Methoden werden synchron ausgeführt. Synchron bedeutet, dass die Clientanwendung nach dem Methodenaufruf so lange wartet, bis das Ergebnis der Anfrage 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.
24.1.3 Begrenzung der Abfragezeit durch CommandTimeout
Wird eine Abfrage mit ExcuteScalar, EcexuteNonQuery oder ExcuteReader ausgeführt, wartet das SqlCommand-Objekt per Vorgabe 30 Sekunden auf das Eintreffen der ersten Abfrageergebnisse (andere Provider können unterschiedliche Zeiten festlegen). 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. Dies ist problematisch, da eine Abfrage durch einen Fehler »unendlich« lange dauern kann. Die Zeit für eine Abfrage beginnt mit dem Senden des Kommandos an den Server und endet mit der Serverantwort.
24.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 MustOverride Function ExecuteNonQuery() As Integer |
Handelt es sich bei dem Befehl um ein UPDATE-, INSERT- oder DELETE-Kommando, stellt der Rückgabewert die Anzahl der von der Anweisung betroffenen Datenzeilen dar.
Datensätze hinzufügen
In der folgenden SQL-Syntax passen Sie die kursiven Namen Ihren Bedürfnissen an (optionale Teile stehen in eckigen Klammern, und Alternativen werden durch | getrennt).
INSERT [INTO] Tabelle[(Spalten)] (VALUES(Werte) | SELECT... | EXECUTE...) |
Das folgende Beispielprogramm fügt der Tabelle Products einen Datensatz hinzu. 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 (DbConnection geht nicht).
'...\ADO\Datenbankabfragen\Insert.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Insert Sub Test() Dim SqlCon As SqlConnection = New SqlConnection("Data Source=.;" & _ "Initial Catalog=Northwind;Integrated Security=sspi") Dim SQL_Befehl As String = _ "INSERT INTO Products(ProductName, Discontinued) " & _ "VALUES('Schweizer Käse',0)" Try Dim cmd As DbCommand = New SqlCommand(SQL_Befehl, SqlCon) SqlCon.Open() Dim zeilen As Integer = cmd.ExecuteNonQuery() Console.WriteLine("{0} Zeile(n) geändert.", zeilen) Catch ex As Exception Console.WriteLine("Fehler: {0}", ex.Message) Finally SqlCon.Close() End Try Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt das Einfügen:
1 Zeile(n) geändert.
Datensätze löschen
In der folgenden SQL-Syntax passen Sie die kursiven Namen Ihren Bedürfnissen an (optionale Teile stehen in eckigen Klammern):
DELETE [FROM] Tabelle [WHERE Bedingung] |
Der Datensatz aus dem vorhergehenden Beispiel soll nun wieder gelöscht werden, und zwar zweifach:
'...\ADO\Datenbankabfragen\Delete.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Delete Sub Test() Dim SqlCon As SqlConnection = New SqlConnection("Data Source=.;" & _ "Initial Catalog=Northwind;Integrated Security=sspi") Dim SQL_Befehl As String = _ "DELETE FROM Products WHERE ProductName='Schweizer Käse'" Try Dim cmd As DbCommand = New SqlCommand(SQL_Befehl, SqlCon) SqlCon.Open() Console.WriteLine("{0} Zeile(n) geändert.", cmd.ExecuteNonQuery()) Console.WriteLine("{0} Zeile(n) geändert.", cmd.ExecuteNonQuery()) Catch ex As Exception Console.WriteLine("Fehler: {0}", ex.Message) Finally SqlCon.Close() End Try Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Wenn Sie in DELETE die WHERE-Klausel vergessen, wird alles gelöscht. |
Nach dem ersten Löschbefehl befindet sich kein der WHERE-Klausel entsprechender Datensatz mehr in der Tabelle, und es wird nichts weiter gelöscht.
1 Zeile(n) geändert. 0 Zeile(n) geändert.
Datensätze ändern
In der folgenden SQL-Syntax passen Sie die kursiven Namen Ihren Bedürfnissen an (optionale Teile stehen in eckigen Klammern, und Alternativen werden durch | getrennt).
UPDATE Tabelle SET Spalte=Wert
[(WHERE (Bedingung | CURRENT OF cursor) | FROM TabelleX WHERE Bedingung)] |
Zu Beginn dieses Abschnitts wurde in dem Beispiel Command bereits gezeigt, wie Sie Datensätze in der Datenbank editieren können.
24.1.5 Abfragen mit einem Einzelergebnis
Mit der SELECT-Anweisung können Sie Datensätze nach bestimmten Auswahlkriterien aus einer Datenbank abrufen. Der Befehl SELECT wird aber auch dann benutzt, wenn eine Aggregatfunktion definiert werden soll, die ein einzelnes Ergebnis zurückliefert. Beispielsweise können Sie mit dem Aggregat
SELECT COUNT(*) FROM Products
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. Tabelle 24.1 zeigt die fünf Standardaggregatfunktionen:
Funktion(Spalten) | Beschreibung |
COUNT |
Anzahl der Werte in der Spalte |
SUM |
Summe der Werte in der Spalte |
AVG |
Mittelwert der Spalte |
MAX |
Größter Wert in der Spalte |
MIN |
Kleinster Wert in der Spalte |
Um den Rückgabewert einer Aggregatfunktion entgegenzunehmen, rufen Sie die Methode ExecuteScalar des SqlCommand-Objekts auf. Der Typ der Rückgabe ist Object, daher muss das Ergebnis noch in den passenden Datentyp konvertiert werden.
Dim strSQL As String = "SELECT COUNT(*) FROM Products WHERE CategoryID=1" Dim cmd As DbCommand = new SqlCommand(strSQL, SqlCon) Dim anzahlDS As Integer = Convert.ToInt32(cmd.ExecuteScalar())
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.