28.6 TableAdapter verwenden
Der TableAdapter ist auf genau eine bestimmte Abfrage spezialisiert. Alle datenbankrelevanten Informationen werden im Code abgelegt, sodass es reicht, wenn Sie den Adapter mit einem parameterlosen Konstruktor instanziieren – eine Parameterübergabe oder die Festlegung von Eigenschaften sind überflüssig. Durch die Spezialisierung arbeitet der TableAdapter nur mit Tabellen zusammen, die den Typ haben, der im generierten typisierten DataSet festgelegt ist. Das typisierte DataSet und der TableAdapter bilden eine logische Einheit, trotzdem liegen beide in unterschiedlichen Namensräumen (aus meiner Sicht ein unglückliches Design).
28.6.1 Datenbeschaffung mit Fill
Die Fill-Methode der Klasse SqlDataAdapter hat eine Reihe von Überladungen. Im Gegensatz dazu erwartet die Fill-Methode des TableAdapters nur die Instanz der typisierten Tabelle. In Abschnitt 28.2.3, »TableAdapter haben Sie bereits ein Beispiel gesehen. Die Eigenschaft ClearBeforeFill des TableAdapters hat den Standardwert True, sodass jeder Aufruf der Fill-Methode die Tabelle vollständig ersetzt. Damit jeder Aufruf von Fill jeweils Datenzeilen anhängt, setzen Sie den Parameter auf False.
28.6.2 Datenbeschaffung mit GetData
Ähnlich wie Fill arbeitet auch die Methode GetData. Allerdings müssen Sie sich vorher keine Instanz des typisierten DataSets besorgen. Der Code wird etwas kürzer.
'...\ADO\DataSetTypisiert\GetData.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module GetData Sub Test() Dim nw As New NWTableAdapterTableAdapters.ProductsTableAdapter() Dim tab As NWTableAdapter.ProductsDataTable = nw.GetData() Dim tab2 As NWTableAdapter.ProductsDataTable = nw.GetData() Console.WriteLine("ProduktID {0}", tab(0).ProductID) Console.WriteLine("ProduktID {0}", tab2(0).ProductID) Console.WriteLine("Dieselben Tabellen: {0}", tab Is tab2) Console.ReadLine() End Sub End Module End Namespace
Wichtig ist, dass jeder Aufruf von GetData eine neue Tabelle erzeugt, wie die letzte Zeile der Ausgabe zeigt:
ProduktID 1 ProduktID 1 Dieselben Tabellen: False
28.6.3 Aktualisierung mit Update
Die Methode Update des TableAdapters synchronisiert die lokalen Daten im übergebenen typisierten DataSet mit der Datenbank. Alternativ kann auch eine DataTable oder eine einzelne Zeile oder ein Array von DataRow abgeglichen werden. Im folgenden Codefragment werden eine neue Kategorie Wein und ein dazugehöriges Produkt Bordeaux dem DataSet hinzugefügt. Danach wird die Categories-Tabelle im DataSet mit der Datenbank abgeglichen. Damit im Fehlerfall die Aktion rückgängig gemacht werden kann, wird die für das Update- Kommando benötigte Einfügeoperation innerhalb einer Transaktion ausgeführt. Dazu wird die Verbindung zur Datenbank geöffnet, eine Transaktion gestartet und für Insert verwendet. Erst danach wird Update aufgerufen. Im zweiten Teil des Abgleichs wird die Products-Tabelle der Datenbank aktualisiert und die Transaktion der Categories-Tabelle mit einem Commit abgeschlossen. Im Fehlerfall ist der Abgleich der Products-Tabelle gescheitert, und die erste Einfügeoperation wird mit Rollback rückgängig gemacht.
'...\ADO\DataSetTypisiert\Transaktion.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Transaktion Sub Test() Dim nw As New NWDataSet() Dim nwc As New NWDataSetTableAdapters.CategoriesTableAdapter() Dim nwp As New NWDataSetTableAdapters.ProductsTableAdapter() nwc.Fill(nw.Categories) nwp.Fill(nw.Products) Dim row As NWDataSet.CategoriesRow = _ nw.Categories.AddCategoriesRow("Wein") nw.Products.AddProductsRow("Bordeaux", row, 12D) nwc.Connection.Open() Dim tr As SqlTransaction = nwc.Connection.BeginTransaction() nwc.Adapter.InsertCommand.Transaction = tr nwc.Update(nw) Try nwp.Update(nw) tr.Commit() Catch ex As Exception Console.WriteLine("Fehler: {0}", ex.Message) tr.Rollback() End Try nwc.Connection.Close() nwc.Fill(nw.Categories) Console.WriteLine("Art: {0}", _ nw.Categories(nw.Categories.Rows.Count – 1).CategoryName) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass etwas schiefgegangen ist, und die letzte Zeile der Categories-Tabelle wurde mit Rollback in den Ursprungszustand versetzt.
Fehler: Die INSERT-Anweisung steht in Konflikt mit der FOREIGN KEY-Einschränkung "FK_Products_Categories". Der Konflikt trat in der "Northwind"-Datenbank, Tabelle "dbo.Categories", column 'CategoryID' auf. Die Anweisung wurde beendet. Art: Seafood
Die Spalte CategoryID der Categories-Tabelle ist ein Autoinkrementwert. Der im lokalen DataSet verwendete Wert hat keinen Bezug zu dem Wert, der von der Datenbank vergeben wird. Damit hat die Zeile in der Methode AddProductsRow() eine CategoryID, die gegebenenfalls in der Datenbank gar nicht existiert. Dies ist aber aufgrund der Fremdschlüsseleinschränkung der Products-Tabelle nicht erlaubt. Die Lösung besteht darin, den neuen Wert von der Datenbank abzuholen und als Referenz zu verwenden. Für den in Abschnitt 28.5.1, »Visual StudioAssistent« erstellten TableAdapter wurde, wie in Abbildung 28.11, »Erweiterte Optionen des TableAdapter-Konfigurationsassistenten«, gezeigt, mit der dritten Option dies automatisch gewährleistet. Für das hier verwendete und in Abschnitt 28.1.1, »Visual Studio Designer«, erzeugte DataSet erfolgt dies im nächsten Codefragment manuell. Dazu wird mit Update die Categories-Tabelle der Datenbank aktualisiert und werden mit Fill die neuen Daten in das DataSet übernommen. Um das Beispiel allgemein zu halten, wird nicht angenommen, dass die neue Zeile als letzte eingefügt wurde, sondern sie wird mit einem LINQ-Ausdruck herausgesucht. Diese aktualisierte Zeile kann nun problemlos in AddProductsRow verwendet werden.
'...\ADO\DataSetTypisiert\Update.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Update Sub Test() Dim nw As New NWDataSet() Dim nwc As New NWDataSetTableAdapters.CategoriesTableAdapter() Dim nwp As New NWDataSetTableAdapters.ProductsTableAdapter() nwc.Fill(nw.Categories) nwp.Fill(nw.Products) Dim row As NWDataSet.CategoriesRow = _ nw.Categories.AddCategoriesRow("Wein") nwc.Update(nw) nwc.Fill(nw.Categories) row = (From cat As NWDataSet.CategoriesRow In nw.Categories _ Where cat.CategoryName = "Wein").First() nw.Products.AddProductsRow("Bordeaux", row, 12D) nwp.Update(nw) Console.WriteLine("Art: {0}", _ nw.Categories(nw.Categories.Rows.Count – 1).CategoryName) Console.WriteLine("Produkt: {0}", _ nw.Products(nw.Products.Rows.Count – 1).ProductName) Console.ReadLine() End Sub End Module End Namespace
Nun ist die Änderung auch in der Datenbank angekommen.
Art: Wein Produkt: Bordeaux
Das TableAdapter-Objekt hat eine Methode Update, um Änderungen an die Datenbank zu übermitteln. Die Methode akzeptiert ein typisiertes DataSet oder eine typisierte DataTable als Argument, ebenso auch eine einzelne DataRow oder ein Array von DataRows. Damit unterscheidet sich die Update-Methode des TableAdapters nur unwesentlich von der Update-Methode des SqlDataAdapters.
28.6.4 Aktualisieren mit UpdateAll
Sollten Sie mehrere Insert-, Update- und Delete-Anweisungen zum Datenbankabgleich benötigen, bietet der TableAdapterManager eine Methode UpdateAll, die sich automatisch um die richtige Reihenfolge der Befehle kümmert. Im folgenden Beispiel werden die im letzten Abschnitt hinzugefügten Zeilen mit einem LINQ-Ausdruck ermittelt und aus dem DataSet gelöscht. Bevor die Methode UpdateAll() des TableAdapterManagers den Datenbankabgleich vornehmen kann, muss dieser noch über die zu verwendenden TableAdapter informiert werden. Die Methode kümmert sich automatisch darum, erst die Detailtabelle Products und dann erst die Mastertabelle Categories zu bearbeiten.
'...\ADO\DataSetTypisiert\UpdateAll.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module UpdateAll Sub Test() Dim nw As New NWDataSet() Dim nwc As New NWDataSetTableAdapters.CategoriesTableAdapter() Dim nwp As New NWDataSetTableAdapters.ProductsTableAdapter() nwc.Fill(nw.Categories) nwp.Fill(nw.Products) Dim cats = From cat As NWDataSet.CategoriesRow In nw.Categories _ Where cat.CategoryName = "Wein" Dim prods = From prod As NWDataSet.ProductsRow In nw.Products _ Where prod.CategoryID = cats.First().CategoryID prods.First().Delete() cats.First().Delete() Dim nwa As New NWDataSetTableAdapters.TableAdapterManager() nwa.ProductsTableAdapter = nwp nwa.CategoriesTableAdapter = nwc nwa.UpdateAll(nw) nwc.Fill(nw.Categories) nwp.Fill(nw.Products) Console.WriteLine("Art: {0}", _ nw.Categories(nw.Categories.Rows.Count – 1).CategoryName) Console.WriteLine("Produkt: {0}", _ nw.Products(nw.Products.Rows.Count – 1).ProductName) Console.ReadLine() End Sub End Module End Namespace
Die Datenbank hat die Änderungen übernommen, und die Änderungen des letzten Abschnitts wurden rückgängig gemacht:
Art: Seafood Produkt: Original Frankfurter grüne Soße
28.6.5 Direkte Aktualisierung der Datenbank
Die Datenbank kann auch direkt ohne lokale Speicherung in einem DataSet geändert werden. In der folgenden Syntax sind optionale Teile in eckige Klammern gesetzt. Alle Funktionen liefern als Ergebnis die Anzahl der betroffenen Zeilen in der Datenbank.
Insert(<Neue Werte ohne Autoinkrement>) Update(<Neue Werte ohne Autoinkrement>, <Datenbankwerte>, [<Temporäre Autoinkrementwerte>]) Delete(<Datenbankwerte>) |
Die kursiv gesetzten Namen sind für jedes typisierte DataSet entsprechend konkretisiert. Hier also ergeben sich aufgrund der Abfrage
SELECT ProductID, ProductName, CategoryID, UnitPrice FROM Products
des typisierten DataSets die folgenden Methoden:
Insert(ProductName, CategoryID, UnitPrice) Update(ProductName, CategoryID, UnitPrice, _ OrigProductID, OrigProductName, OrigCategoryID, OrigUnitPrice _ [, ProductID]) As Integer Delete(OrigProductID, OrigProductName, OrigCategoryID, OrigUnitPrice)
Das folgende Codefragment zeigt die drei Funktionen in Aktion. Beim Einfügen der neuen Zeilen wird der Spalte ProductID von der Datenbank automatisch ein Wert zugewiesen. Um diesen bei den Update- und Delete-Kommandos zur Verfügung zu haben, wird die Datenbank abgefragt und werden die neuen Werte in p und s abgelegt.
'...\ADO\DataSetTypisiert\Direkt.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Direkt Sub Test() Dim nw As New NWTableAdapter() Dim ta As New NWTableAdapterTableAdapters.ProductsTableAdapter() ta.Insert("Printen", 2D) ta.Insert("Marzipan", 2D) ta.Fill(nw.Products) Dim p = (From prod As NWTableAdapter.ProductsRow In nw.Products _ Where prod.ProductName = "Printen").First().ProductID Dim s = (From prod As NWTableAdapter.ProductsRow In nw.Products _ Where prod.ProductName = "Marzipan").First().ProductID ta.Update("Öcher Prente", 3D, p, "Printen", 2D, p) ta.Update("Stippefötche", 4D, s, "Marzipan", 2D, s) ta.Delete(s, "Stippefötche", 4D) ta.Fill(nw.Products) For Each r As NWTableAdapter.ProductsRow In _ nw.Products.Skip(nw.Products.Rows.Count – 2) Console.WriteLine("Produkt: {0}", r.ProductName) Next ta.Delete(p, "Öcher Prente", 3D) 'Originalzustand herstellen Console.ReadLine() End Sub End Module End Namespace
Von den beiden eingefügten Zeilen ist eine vor der Ausgabe der beiden letzten Tabellenzeilen bereits gelöscht worden.
Produkt: Original Frankfurter grüne Soße Produkt: Öcher Prente
Hinweis |
Wird eine Zeile aufgrund falscher Werte nicht in der Datenbank gefunden, wird das entsprechende Kommando ignoriert. Es wird keine Ausnahme ausgelöst. Den Erfolg prüfen Sie mit dem Rückgabewert der Änderungsmethoden (Anzahl betroffener Zeilen). |
28.6.6 TableAdapter mit mehreren Abfragen
Zusätzlich zur eigentlichen Abfrage eines TableAdapters können noch Funktionen generiert werden, die Sichten auf diese Daten definieren und damit eine Auswahl der gesamten Daten zur Verfügung stellen. Eine Erweiterung der Daten ist nicht möglich. Als Beispiel zeige ich, wie Sie aus der Products-Tabelle nur Produkte einer bestimmten Kategorie auswählen. Öffnen Sie dazu im Visual Studio den Designer des typisierten DataSets, und markieren Sie den TableAdapter, der die Tabelle Products beschreibt. Einen Assistenten starten Sie über den Menüpunkt Abfrage hinzufügen des Kontextmenüs des Adapters. Im ersten Dialog entscheiden Sie sich, eine SQL-Anweisung zu verwenden, eine gespeicherte Prozedur zu erstellen oder eine vorhandene gespeicherte Prozedur zu verwenden.
Wir wählen die erste Option und geben im nächsten Schritt den Abfragetyp an. Damit wir Zeilen derselben Kategorie erhalten, entscheiden wir uns mit der obersten Option für eine normale SELECT-Abfrage (siehe Abbildung 28.14).
Abbildung 28.14 Festlegen des Abfragetyps
Bestätigen Sie mit Weiter, wird die Basisabfrage des TableAdapters angezeigt. Diese können Sie erweitern. Tragen Sie also
WHERE CategoryID = @CategoryID
in das Fenster ein, oder benutzen Sie den Abfrage-Generator (siehe Abbildung 28.15).
Abbildung 28.15 Ergänzung der Basisabfrage des TableAdapters
Im folgenden Dialog (siehe Abbildung 28.16) werden FillBy und GetDataBy als Namen für Fill und GetData der Sicht auf die Daten vorgeschlagen, die wir in die sprechenden Namen FillByCategoryID und GetDataByCategoryID abändern. Wir schließen die Codegenerierung ab, indem wir auf den Button Fertig stellen klicken.
Abbildung 28.16 Methoden »Fill« und »GetData« benennen
Das Ergebnis sehen Sie anschließend im Designer. Es liegen jetzt zwei parametrisierte Abfragen vor, denen wir CategoryID als Argument übergeben müssen (siehe Abbildung 28.17).
Abbildung 28.17 TableAdapter mit einer hinzugefügten Abfrage
Testen wir zuerst die Methode FillByCategoryID. Hierzu benötigen wir je eine Instanz des typisierten DataSets sowie des TableAdapters, um die Methode FillByCategoryID des TableAdapter-Objekts aufzurufen. Neben der zu füllenden Tabelle wird der Methode als zweiter Parameter die Kategorie übergeben, die wir heraussuchen wollen.
Dim nw As New NWDataSet() Dim nwa As New NWDataSetTableAdapters.ProductsTableAdapter() nwa.FillByCategoryID(nw.Products, 5) For Each zeile As NWDataSet.ProductsRow In nw.Products Console.WriteLine("Produkt {0} in Kategorie {1} ", _ zeile.ProductName, zeile.CategoryID) Next
Die Methode GetData erfordert etwas weniger Code, und die Instanz des typisierten DataSets wird nicht benötigt. Auch diese Methode erwartet die Kategorienummer.
Dim nwa As New NWDataSetTableAdapters.ProductsTableAdapter() Dim p As NWDataSet.ProductsDataTable = nwa.GetDataByCategoryID(5) For Each zeile As NWDataSet.ProductsRow In p Console.WriteLine("Produkt {0} in Kategorie {1} ", _ zeile.ProductName, zeile.CategoryID) Next
28.6.7 TableAdapter ändern
Wenn Sie den TableAdapter im Designer markieren, werden dessen Eigenschaften im Eigenschaftsfenster von Visual Studio angezeigt (siehe Abbildung 28.18). Sie können hier nicht nur die Verbindungsinformationen neu festlegen, sondern auch die SELECT-Abfrage. Ergänzen Sie diese beispielsweise um eine Spalte, werden die Aktualisierungsabfragen UPDATE, INSERT und DELETE nach vorheriger Bestätigung der Änderung angepasst.
Besonders interessant sind die Manipulationsmöglichkeiten, die sich hinter den Eigenschaften DeleteCommand und UpdateCommand verbergen. Da Sie dort die WHERE-Klausel festlegen können, haben Sie hier Einfluss auf das Verhalten im Konfliktfall.
Abbildung 28.18 Das Eigenschaftsfenster eines TableAdapters
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.