26.3 Änderung einer Tabelle 

Sehen wir uns nun an, wie wir einer DataTable eine neue DataRow hinzufügen, eine vorhandene löschen bzw. editieren können. Um einen wichtigen Punkt gleich vorwegzunehmen: Jegliche Änderung betrifft zunächst nur das DataSet. Die Originaldatenbank weiß davon nichts. Erst zu einem späteren Zeitpunkt werden alle Änderungen zur Datenbank übermittelt. Wir behandeln in diesem Abschnitt nur die lokalen Aktualisierungen.
26.3.1 Editieren einer Datenzeile 

Es gibt drei Möglichkeiten, eine Zeile zu aktualisieren. Im einfachsten Fall weisen Sie der betreffenden Spalte nur den neuen Inhalt zu, zum Beispiel:
ds.Tables(0).Rows(3)("ProductName") = "Kirschkuchen"
Die Änderung wird sofort in die angegebene Spalte der entsprechenden Datenzeile der DataTable geschrieben.
Eine zweite Variante puffert die Änderung. Dazu wird vor Beginn der Änderung die Methode BeginEdit auf der zu ändernden Datenzeile aufgerufen und die Änderung mit EndEdit bestätigt. Sie können die eingeleitete Änderung auch zurücksetzen und anstelle von EndEdit die Methode CancelEdit aufrufen. Die Zeile wird dann in den Zustand zurückversetzt, den sie vor BeginEdit hatte.
Dim row As DataRow = ds.Tables(0).Rows(3)
row.BeginEdit()
row("ProductName") = "Kirschkuchen"
row.EndEdit()
' alternativ: row.Canceledit()
Die dritte Möglichkeit bietet uns die Eigenschaft ItemArray, mit der Sie den Inhalt einer Datenzeile abrufen oder verändern können. ItemArray arbeitet mit einem Array, in dem jedes Element einer Spalte entspricht. Mit einer Codezeile können Sie mehrere Spaltenwerte abrufen und editieren. Ist in einer Zeile nur eine Teilmenge der verfügbaren Werte zu modifizieren, verwenden Sie Nothing, um anzuzeigen, dass der Wert dieser Spalte nicht geändert werden soll. Jedes Element ist vom Typ Object.
Im folgenden Codefragment werden drei Spalten der Products-Tabelle abgefragt. In der ersten Datenzeile wird mit der Eigenschaft ItemArray der Produktbezeichner modifiziert. Weil der Schlüsselwert nicht geändert wird, muss an der ersten Position Nothing in das Objekt-Array geschrieben werden.
Dim cmd As DbCommand = new SqlCommand()
cmd.Connection = con
cmd.CommandText = "SELECT ProductID, ProductName, UnitPrice FROM Products"
Dim ds As DataSet = New DataSet()
Dim da As DbDataAdapter = New SqlDataAdapter(cmd)
da.Fill(ds)
Dim row As DataRow = ds.Tables(0).Rows(0)
row.ItemArray = New Object() {Nothing, "Kirschkuchen"}
NULL-Spaltenwert
Möchten Sie den Wert einer Spalte auf NULL setzen, verwenden Sie die Klasse DBNull, die sich im Namespace System befindet. Mit der Eigenschaft Value legen Sie den Wert einer Spalte in einer DataRow auf NULL fest:
Dim row As DataRow = ds.Tables(0).Rows(4)
row("UnitPrice") = DBNull.Value
Ereignisse bei der Änderung einer Datenzeile
Die DataTable verfügt über mehrere Ereignisse, die in Tabelle 26.4 aufgelistet sind.
Ereignis | Auslösung |
RowChanging |
Vor dem Eintragen der Änderung an einer Datenzeile in das DataSet |
RowChanged |
Nach dem Eintragen der Änderung an einer Datenzeile in das DataSet |
ColumnChanging |
Vor dem Eintragen der Änderung in einer Spalte in das DataSet |
ColumnChnaged |
Nach dem Eintragen der Änderung in einer Spalte in das DataSet |
Die Ereignisse spielen eine Rolle, wenn Änderungen an einer Datenzeile oder Spalte überprüft werden müssen. Sie werden nicht ausgelöst, wenn Sie CancelEdit aufrufen.
Die Ursache für die Ereignisse RowChanging und RowChanged können Sie mittels der Eigenschaft Action des DataRowChangeEventArgs-Parameters abfragen. Sie kann die Werte Nothing, Delete, Change, Rollback, Commit, Add, ChangeOriginal und ChangeCurrentAndOriginal annehmen. Mit der Eigenschaft Row des Parameters erhalten Sie zudem die Referenz auf die auslösende Datenzeile.
Die Ereignisse ColumnChanging und ColumnChanged liefern im Parameter DataColumnChangeEventArgs in der Eigenschaft Row ebenfalls die Referenz auf die zu ändernde bzw. geänderte Datenzeile, die Eigenschaft Column liefert darüber hinaus die Referenz auf die geänderte Spalte. Den neuen Wert können Sie der Eigenschaft ProposedValue entnehmen.
Das Beispiel Ereignisse registriert vier Ereignishandler, die durch die nachfolgende Zuweisung angesprochen werden:
'...\ADO\DataSet\Ereignisse.vb |
Option Strict On
Imports System.Data.Common, System.Data.SqlClient
Namespace ADO
Module Ereignisse
Sub Test()
Dim con As DbConnection = New SqlConnection()
con.ConnectionString = "Data Source=(local);" & _
"Initial Catalog=Northwind;Integrated Security=sspi"
Dim cmd As DbCommand = New SqlCommand()
cmd.CommandText = "SELECT Top 2 ProductID, ProductName FROM Products"
cmd.Connection = con
Dim ds As New DataSet()
Dim da As DbDataAdapter = New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds)
AddHandler ds.Tables(0).ColumnChanging, AddressOf Spalte
AddHandler ds.Tables(0).ColumnChanged, AddressOf Spalte
AddHandler ds.Tables(0).RowChanging, AddressOf Zeile
AddHandler ds.Tables(0).RowChanged, AddressOf Zeile
ds.Tables(0).Rows(0)("ProductName") = "Tee"
Console.ReadLine()
End Sub
Sub Zeile(ByVal sender As Object, ByVal ev As DataRowChangeEventArgs)
Console.WriteLine("Zeilenaktion: {0}", ev.Action)
Console.WriteLine("Wert: {0}", ev.Row("ProductName"))
End Sub
Sub Spalte(ByVal sender As Object, ByVal ev As DataColumnChangeEventArgs)
Console.WriteLine("Spalte: {0}", ev.Column.ColumnName)
Console.WriteLine("Neuer Wert: {0}", ev.ProposedValue)
Console.WriteLine("Wert: {0}", ev.Row("ProductName"))
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die Spaltenänderung abgeschlossen wird, bevor die Zeilenänderung behandelt wird:
Spalte: ProductName
Neuer Wert: Tee
Wert: Alice Mutton
Spalte: ProductName
Neuer Wert: Tee
Wert: Tee
Zeilenaktion: Change
Wert: Tee
Zeilenaktion: Change
Wert: Tee
26.3.2 Löschen einer Datenzeile 

Zum Löschen einer Datenzeile rufen Sie einfach Delete auf der zu löschenden DataRow auf:
row.Delete()
Die Datenzeile ist nicht aus der DataTable entfernt, sondern ADO.NET kennzeichnet sie als gelöscht. Der Hintergrund der Markierung ist, dass das Löschen zunächst nur das aktuelle DataSet betrifft und zu einem späteren Zeitpunkt der Originaldatenbank mitgeteilt werden muss. Es ist daher auch falsch, eine Datenzeile mit Remove oder RemoveAt aus der DataRow-Collection der Tabelle zu entfernen, denn dann findet der Aktualisierungsprozess die Datenzeile nicht mehr.
Auch beim Löschen treten zwei Ereignisse auf: RowDeleting und RowDeleted. Beide verwenden die gleichen Eigenschaften und Argumente wie RowChanging und RowChanged.
26.3.3 Neue Datenzeile hinzufügen 

Eine Datenzeile zu einer DataTable hinzuzufügen, ist nicht schwierig. Allerdings stellt die Klasse DataRow keinen öffentlichen Konstruktor zur Verfügung, denn woher sollte ein auf diese Weise konstruiertes DataRow-Objekt etwas von den Spalten wissen, die es beschreibt?
ADO.NET bietet Ihnen analog zum Editieren einer Datenzeile drei Varianten an, um eine neue Datenzeile zu einer DataTable hinzuzufügen. Eine mit der Methode NewRow der DataTable erzeugte Zeile enthält alle Informationen über die Spalten in der Tabelle. Wenn im Schema keine Standardwerte vorgegeben werden, sind die Inhalte der Spalten auf NULL gesetzt. Haben Sie alle Einträge in der neuen Zeile vorgenommen, müssen Sie die neue Zeile an die DataRowCollection anhängen, denn das leistet der Aufruf von NewRow nicht.
Dim tbl As DataTable = ds.Tables(0)
Dim row As DataRow = tbl.NewRow()
row("ProductName") = "Erbsensuppe"
row("UnitPrice") = 2
row("SupplierID") = 3
' ...
tbl.Rows.Add(row)
Eine Überladung der Methode Add der DataRowCollection ist die zweite Variante zur Erzeugung neuer Zeilen. Übergeben Sie dem Methodenaufruf die Spaltenwerte in derselben Reihenfolge wie in der SELECT-Abfrage. Basierend auf der Auswahlabfrage
SELECT ProductName, Unitprice, UnitsInStock FROM Products
könnte eine neue Datenzeile wie folgt hinzugefügt werden:
ds.Tables(0).Rows.Add("Mehl", 20)
Im Gegensatz zur Methode NewRow wird die neue Datenzeile automatisch der DataRowCollection hinzugefügt.
Die dritte Möglichkeit ist die Methode LoadDataRow der DataTable. Sie arbeitet ähnlich wie die zuvor gezeigte Add-Methode der DataRowCollection, verlangt aber die Angabe von zwei Parametern. Geben Sie im ersten Parameter ein Array von Werten an, dessen Elemente den Spalten in der Tabelle entsprechen. Tragen Sie im zweiten Parameter False ein, um die so gekennzeichnete Datenzeile als neue Datenzeile zu interpretieren. Der Wert True ermöglicht es, eine bestimmte Datenzeile zu suchen und zu modifizieren.
ds.Tables(0).LoadDataRow(New Object() {"Mehl", 20, 0}, False)
Sonderfall: Autoinkrementspalten
Viele Tabellen in Datenbanken besetzen das Primärschlüsselfeld mit Autoinkrementwerten. Diese garantieren automatisch, dass alle Einträge einmalig sind. Fügen wir jedoch eine neue Datenzeile zu einer DataTable hinzu, die ein solches Schlüsselfeld definiert, haben wir keine Verbindung zur Originaldatenbank, sodass wir den neuen Wert des Schlüsselfeldes nicht kennen, bis wir die Datenbank aktualisieren und die Datenbankwerte erneut abfragen.
ADO.NET unterstützt uns mit drei Eigenschaften der DataColumn, um auch diese scheinbare Problematik zu lösen:
- AutoIncrement
- AutoIncrementSeed
- AutoIncrementStep
Um in einer DataTable Autoinkrementwerte von ADO.NET generieren zu lassen, muss die Eigenschaft AutoIncrement der betreffenden Spalte auf True gesetzt werden. Mit AutoIncrementSeed und AutoIncrementStep werden die von ADO.NET erzeugten Werte gesteuert. AutoIncrementSeed beschreibt den Startwert für die erste neu hinzugefügte Datenzeile. AutoIncrementStep gibt die Schrittweite an, mit der neue Schlüsselwerte generiert werden. Zum Beispiel erzeugt eine Autoinkrementspalte mit AutoIncrementSeed=1 und AutoIncrementStep=2 die Werte 1, 3 und 5 in der Autoinkrementspalte für die drei nächsten hinzugefügten Datenzeilen.
Die Werte, die ADO.NET erzeugt, sind nur Platzhalter. Sie werden später bei der Aktualisierung der Originaldatenbank nicht mit zurückgeschrieben. Die tatsächlichen Schlüsselwerte erzeugt die Datenbank selbst. Sie müssen sicherstellen, dass neue Schlüsselwerte nicht mit den alten in Konflikt geraten. Meistens verwenden Datenbanken keine negativen Werte, sodass die Festlegung der beiden Eigenschaften AutoIncrementSeed und AutoIncrementStep auf jeweils -1 ein guter Ausgangspunkt ist. Diese Einstellungen müssen erfolgen, ehe das DataSet mit den Daten gefüllt wird. Im folgenden Beispiel ist der Rahmen gezeigt:
'...\ADO\DataSet\AutoInkrement.vb |
Option Strict On
Imports System.Data.Common, System.Data.SqlClient
Namespace ADO
Module AutoInkrement
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.Connection = con
Dim da As DbDataAdapter = New SqlDataAdapter()
Dim ds As DataSet = Create(cmd, da)
Dim row As DataRow, tbl As DataTable = ds.Tables(0)
row = tbl.NewRow() : row("ProductName") = "Saft" : tbl.Rows.Add(row)
row = tbl.NewRow() : row("ProductName") = "Limo" : tbl.Rows.Add(row)
Dim cb As DbCommandBuilder = New SqlCommandBuilder()
cb.DataAdapter = da
da.Update(ds)
Print(ds)
ds.Clear() : da.Fill(ds) : Print(ds)
Drop(cmd)
Console.ReadLine()
End Sub
...
End Module
End Namespace
Nach der Initialisierung der Verbindung, des Kommandos und des DataAdapters wird in der Methode Create eine Tabelle zum Testen erzeugt. Dieser Tabelle werden dann zwei neue Zeilen hinzugefügt, und die Datenbank wird mit Update aktualisiert (der Grund für den CommandBuilder wird im nächsten Abschnitt erläutert). Schließlich werden die lokale Tabelle und die erneut von der Datenbank ausgelesene Tabelle mit Print ausgegeben. In Create wird eine neue Tabelle Produkte erzeugt, die erst nach dem Einlesen und nochmaligen Abfragen bekannt ist. Vor dem endgültigen Befüllen wird die Autoinkrementierung parametrisiert (FillSchema hat bereits AutoIncrement=True gesetzt).
'...\ADO\DataSet\AutoInkrement.vb |
Option Strict On
Imports System.Data.Common, System.Data.SqlClient
Namespace ADO
Module AutoInkrement
...
Sub Print(ByVal ds As DataSet)
Console.WriteLine("Produkte: ")
For Each row As DataRow In ds.Tables(0).Rows
Console.WriteLine("{0,3}{1,35}", row(0), row(1))
Next
End Sub
Function Create(ByVal cmd As DbCommand, ByVal da As DbDataAdapter) As DataSet
cmd.CommandText = _
"SELECT TOP 2 ProductID, ProductName INTO Produkte FROM Products"
Dim ds As New DataSet()
da.SelectCommand = cmd : da.Fill(ds)
cmd.CommandText = "SELECT * FROM Produkte"
ds = New DataSet() : da.FillSchema(ds, SchemaType.Source)
ds.Tables(0).Columns(0).AutoIncrementSeed = –1
ds.Tables(0).Columns(0).AutoIncrementStep = –1
da.Fill(ds)
Return ds
End Function
Sub Drop(ByVal cmd As DbCommand)
cmd.CommandText = "DROP TABLE Produkte"
cmd.Connection.Open() : cmd.ExecuteNonQuery() : cmd.Connection.Close()
End Sub
End Module
End Namespace
Die erste Ausgabe zeigt die lokalen Nummern, die zweite die endgültig von der Datenbank vergebenen:
Produkte:
17 Alice Mutton
3 Aniseed Syrup
–1 Saft
–2 Limo
Produkte:
18 Saft
19 Limo
17 Alice Mutton
3 Aniseed Syrup
Die generierten Schlüsselwerte sind nur Platzhalter innerhalb der DataTable. Erst nach der Übermittlung zur Originaldatenbank werden die endgültigen Schlüsselwerte von der Datenbank erzeugt. Sie sollten daher vermeiden, die temporären Schlüsselwerte dem Anwender anzuzeigen. Es könnte zu Missverständinssen führen, wenn der Anwender sich eine ADO.NET-Schlüsselnummer notiert, die später nach der Aktualisierung nicht mehr existiert.
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.