36.5 Der »TableAdapter«
TableAdapter-Objekte habe ich in den Beispielen der letzten Seiten schon verwendet, ohne dass ich näher darauf eingegangen bin. Nun wird es Zeit, das nachzuholen.
Der TableAdapter ist eine Klasse, die Sie vergeblich in der Dokumentation suchen werden. Er wird nur vom Designer von Visual Studio erzeugt und nimmt eine besondere Stellung im Zusammenhang mit dem typisierten DataSet ein. Seine besondere Stellung können Sie auch daran erkennen, dass der TableAdapter trotz ähnlicher operativer Fähigkeiten nicht von der Klasse DbDataAdapter abgeleitet ist, wie beispielsweise alle DataAdapter. Stattdessen ist seine Basisklasse System.ComponentModel.Component. Ziehen Sie z. B. die Spalten ProductID, ProductName und UnitPrice der Tabelle Products in den Designer, was dem SQL-Statement
SELECT ProductID, ProductName, UnitPrice FROM Products
entspricht, enthält der TableAdapter ein SqlDataAdapter-Objekt, das entsprechend dem SQL-Statement konfiguriert ist. Mit einem TableAdapter können Sie somit eine DataTable in einem typisierten DataSet füllen oder die in einer DataTable anstehenden Änderungen zur Datenbank übermitteln.
36.5.1 Einen »TableAdapter« mit Visual Studio erzeugen
Wenn Sie ein neues typisiertes DataSet generieren, indem Sie aus dem Server-Explorer Tabellen oder Spalten per Drag & Drop in den Designer ziehen, wird in der zugrunde liegenden Quellcodedatei neben der Klasse des typisierten DataSets auch eine TableAdapter-Klasse erzeugt – und zwar für jede Tabelle des typisierten DataSets genau ein TableAdapter. Dieses Verfahren habe ich Ihnen am Anfang des Kapitels bereits gezeigt.
Ein TableAdapter lässt sich aber auch über Visual Studio 2012 erzeugen. Legen Sie dazu zuerst ein leeres typisiertes DataSet an. Wie Sie sich vielleicht erinnern, markieren Sie dazu das Projekt im Projektmappen-Explorer und wählen Neues Element hinzufügen. Im Vorlagendialog suchen Sie die Vorlage DataSet. Einen TableAdapter können Sie nun über das Menü Daten, das Kontextmenü des Designers oder durch Ziehen des Elements TableAdapter in den Designer bereitstellen. Danach öffnet sich ein Assistent, der Sie durch alle Konfigurationsschritte führt. Abbildung 36.6 zeigt dessen erste Seite. Hier wählen Sie entweder eine im Server-Explorer verfügbare Verbindung aus oder richten über die Schaltfläche Neue Verbindung eine neue ein.
Abbildung 36.6 Dialog zum Konfigurieren der Verbindung eines »TableAdapters«
Nachdem Sie die Verbindung eingerichtet haben, verlangt der Assistent im nächsten Schritt die Angabe des Befehlstyps. Sie können hier eine SQL-Anweisung angeben, eine neue gespeicherte Prozedur erstellen oder eine schon vorhandene gespeicherte Prozedur auswählen, um Daten abzurufen, zu aktualisieren, zu löschen oder hinzuzufügen.
Abbildung 36.7 Festlegen des Befehlstyps
Unabhängig davon, für welche Option Sie sich entscheiden, werden Sie vom Assistenten weiter begleitet. Ich möchte Ihnen an dieser Stelle den Weg zeigen, sollten Sie sich für die oberste Option (SQL-Anweisungen verwenden) entschieden haben. Im dann folgenden Dialog geben Sie das SQL-Statement ein. Sollten Sie aber weiterhin dem Assistenten vertrauen, können Sie auch auf die Schaltfläche Abfrage-Generator klicken.
Abbildung 36.8 Erstellen einer SQL-Anweisung im Assistenten
Über die Schaltfläche Abfrage-Generator gelangen Sie zu einem Dialogfenster, in dem Sie zwischen Tabellen, Ansichten, Funktionen und Synonymen der entsprechenden Datenbank auswählen können. Unter Ansichten finden Sie alle Tabellen wieder. Markieren Sie die Tabelle, für die der TableAdapter erzeugt werden soll, und bestätigen Sie Ihre Wahl mit Hinzufügen. Schließen Sie dann den Dialog.
Abbildung 36.9 Die Tabelle für den »TableAdapter« festlegen
Nachdem Sie das in Abbildung 36.9 gezeigte Dialogfenster geschlossen haben, können Sie im nächsten Fenster das SQL-Statement spezifizieren (siehe Abbildung 36.10). In der oberen Hälfte sehen Sie eine grafische Anzeige der ausgewählten Tabelle mit allen ihren Spalten. Wollen Sie alle Spalten in die Abfrage aufnehmen, genügt es, ein Häkchen vor den Listeneintrag *(Alle Spalten) zu setzen. Interessieren Sie sich nur für bestimmte Spalten, markieren Sie nur die betreffenden.
Im zweiten Block des Dialogs sind alle ausgewählten Spalten aufgeführt. Sie haben in diesem Block noch die Möglichkeit, spezifische Spaltenbezeichner vorzugeben, die Sortierungsart und Sortierreihenfolge festzulegen sowie Auswahlkriterien zu bestimmen. Das resultierende SQL-Statement sehen Sie im dritten Block von oben.
Zum Schluss möchten Sie vielleicht auch noch testen, ob die erzeugte SQL-Anweisung auch das erwartete Ergebnis liefert. Klicken Sie dazu auf die Schaltfläche Abfrage ausführen, und Sie sehen das Ergebnis im untersten Block des Dialogfensters (siehe Abbildung 36.10). Entspricht es Ihren Erwartungen, schließen Sie das Fenster mit OK.
Abbildung 36.10 Abfrage-Generator zum Erstellen einer Abfrage
Nun gelangen Sie wieder zu dem Dialogfenster zurück, das Sie in Abbildung 36.8 sehen können. Für Szenarien mit Aktualisierungen stellt der TableAdapter-Konfigurationsassistent weitere Optionen bereit. Diese erreichen Sie, wenn Sie auf die Schaltfläche Erweiterte Optionen klicken. Der Dialog, der danach geöffnet wird, ist in Abbildung 36.11 zu sehen.
Abbildung 36.11 Die erweiterten Optionen des »TableAdapter«-Konfigurationsassistenten
Sollten Sie Ihren TableAdapter nur dafür benötigen, Dateninformationen aus der Datenbank abzurufen, können Sie das erste Kontrollkästchen (Insert-, Update- und Delete-Anweisungen generieren) deaktivieren. Die anderen angebotenen Optionen werden dann automatisch ebenfalls deaktiviert.
Die Einstellung Vollständige Parallelität verwenden beschreibt die Parallelitätsoption. Wenn Sie die Markierung entfernen, nimmt der Assistent nur die Primärschlüsselspalten der Tabelle in der WHERE-Klausel auf. Behalten Sie die Option bei, werden alle Spalten der SELECT-Abfrage in der WHERE-Klausel von INSERT, UPDATE und INSERT verwendet.
Die dritte und letzte Option, Datentabelle aktualisieren, ist nur dann verfügbar, wenn die abgefragte Datenbank Batchabfragen unterstützt. Bekanntlich gehört der SQL Server zu dieser Gruppe. Ist die Option aktiviert, erzeugt der Assistent Abfragen, um den Inhalt der geänderten Datenzeilen nach Übermittlung der Änderungen sofort wieder abzurufen. Neue Werte, die serverseitig generiert werden (z. B. Autoinkrementwerte von Primärschlüsselspalten), sind sofort in der DataRow verfügbar, nachdem Sie die Update-Methode aufgerufen haben.
Schließen Sie nach Festlegung der erweiterten Optionen den Dialog. Sie haben jetzt die SQL-Anweisungen vollständig definiert und werden im nächsten Schritt des Assistenten die für den TableAdapter verfügbaren Methoden festlegen können (siehe Abbildung 36.12).
Per Vorgabe erzeugt der Assistent eine Fill-Methode, die die entsprechende DataTable im typisierten DataSet mit dem Resultat der Abfrage füllt. Die Methode GetData gibt eine neue Instanz der typisierten DataTable zurück, die zugleich das Ergebnis der Abfrage enthält. Beide Methoden können Sie auch nach eigenem Ermessen umbenennen.
Mit der dritten Option werden Methoden erzeugt, um Werte direkt zur Datenbank zu senden, ohne dabei eine DataRow zu erzeugen. Diese sogenannten DBDirect-Methoden werden wir uns später noch einmal ansehen. Damit ist der TableAdapter konfiguriert. Klicken Sie auf die Schaltfläche Weiter des in Abbildung 36.12 gezeigten Dialogs.
Abbildung 36.12 Die Methoden des »TableAdapters« festlegen
36.5.2 Die Methode »Fill« des »TableAdapters«
TableAdapter haben die Aufgabe, ein typisiertes DataSet zu füllen. Ein TableAdapter vereinfacht dieses Unterfangen, weil Sie bis auf die Instanziierung nichts weiter machen müssen – weder Eigenschaften festlegen noch irgendwelche Argumente an Parameterlisten übergeben.
Per Vorgabe werden TableAdapter zusammen mit der Klasse des typisierten DataSets erzeugt. Dabei ist zu beachten, dass die Klassen der TableAdapter einem anderen Namespace zugeordnet sind als das typisierte DataSet. Damit die Ausdrücke nicht zu lang werden, sollten Sie den Namespace des oder der TableAdapter mit using bekannt geben.
using MyApplication.NWDataSetTableAdapters;
// Typisiertes DataSet erzeugen
NWDataSet ds = new NWDataSet();
// TableAdapter instanziieren
ProductsTableAdapter productsTA = new ProductsTableAdapter();
// Tabelle 'Products' des typisierten DataSets
// mit der Methode Fill des TableAdapters füllen
productsTA.Fill(ds.Products);
Die Fill-Methode der Klasse SqlDataAdapter wartet mit einer Reihe von Überladungen auf. Im Gegensatz dazu erwartet die Fill-Methode des TableAdapters nur die Instanz der typisierten Tabelle.
Rufen Sie mehrfach hintereinander die Fill-Methode des TableAdapters auf, werden die abgefragten Datenzeilen nicht einfach nur hinzugefügt, weil die »alten« Inhalte der typisierten DataTable gelöscht werden. Dieses Verhalten wird von der Eigenschaft ClearBeforeFill des TableAdapters gesteuert, die per Vorgabe true gesetzt ist. Möchten Sie die mit Fill abgefragten Datenzeilen an den bestehenden Inhalt anhängen, müssen Sie die Eigenschaft vorher auf false setzen.
36.5.3 Die Methode »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.
ProductsTableAdapter productsTA = new ProductsTableAdapter();
NWDataSet.ProductsDataTable tbl = productsTA.GetData();
foreach (NWDataSet.ProductsRow row in tbl)
Console.WriteLine("{0,-35}{1}", row.ProductName, row.UnitPrice);
36.5.4 Die Methode »Update«
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.
Eine fünfte Überladung habe ich noch nicht erwähnt. Aber diese ist den DBDirect-Methoden zuzurechnen, die ich Ihnen jetzt vorstellen möchte.
36.5.5 Aktualisieren mit den DBDirect-Methoden
Der TableAdapter verfügt über die Methoden Insert, Update und Delete. Diese Methoden erlauben es, eine Änderung zur Datenbank zu übermitteln, ohne dass dafür eine Änderung an den Datenzeilen in der typisierten DataTable erfolgen muss.
Nehmen wir an, der TableAdapter beschreibt das folgende SQL-Statement:
SELECT ProductID, ProductName, CategoryID, UnitPrice FROM Products
Möchten Sie auf Basis dieser Abfrage einen Datensatz editieren, löschen oder hinzufügen, müssten Sie nach Ihren bisherigen Kenntnissen zunächst ein DataSet füllen und würden anschließend die Datenbank aktualisieren. Mit den DBDirect-Methoden können Sie auf das Füllen des DataSets verzichten und die gewünschten Änderungen direkt der Datenbank übermitteln. Wie Sie die DBDirect-Methoden einsetzen, zeigt das folgende Codefragment. Ausgangspunkt dafür sei die erste Datenzeile der Tabelle Products, die folgende Werte enthält:
ProductID = 1
ProductName = Chai
CategoryID = 8
UnitPrice = 18.000
Sie können mit den beiden folgenden Codezeilen den Produktnamen durch Aufruf der Update-Methode ändern:
ProductsTableAdapter productsTA = new ProductsTableAdapter();
productsTA.Update("Möhren", 8, 100, 1, "Chai", 8, (decimal)18.0, 1);
Dem TableAdapter ist die SQL-Abfrage bekannt, anhand derer er die Parameterliste der DBDirect-Methoden definiert. In diesem Beispiel werden den ersten vier Parametern der Reihe nach die Werte für ProductName, CategoryID, UnitPrice und ProductID übergeben. Aktualisierungen, die Sie an der Datenzeile vornehmen wollen, übergeben Sie dieser Parametergruppe. Den letzten vier Parametern teilen Sie die ursprünglichen Originalwerte mit, die beim Aktualisierungsvorgang zur Identifizierung der Datenzeile in der Datenbank dienen.
Wird die Datenzeile in der Datenbank nicht gefunden, wird keine Exception ausgelöst. Um darüber Kenntnis zu erlangen, ob die Aktualisierung erfolgreich verlaufen ist, können Sie den Rückgabewert abfragen, der die Anzahl der in der Datenbank aktualisierten Datenzeilen widerspiegelt.
36.5.6 TableAdapter mit mehreren Abfragen
TableAdapter genießen im Vergleich zu einem SqlDataAdapter den Vorzug, dass sie mehrere Abfragen unterstützen. Die Basis bildet hierbei immer das Schema des TableAdapters. Mit anderen Worten: Sie müssen die Spalten der »Basisabfrage« beibehalten (z. B. in unserem Beispiel bei der Tabelle Products die Spalten ProductID, ProductName, CategoryID und UnitPrice), können diese Spalten aber nach verschiedenen Kriterien selektieren.
Verdeutlichen wir uns das Gesagte, und nehmen wir an, dass wir uns nur Produkte einer bestimmten Kategorie anzeigen lassen wollen. Öffnen Sie dazu in Visual Studio den Designer des typisierten DataSets, und markieren Sie den TableAdapter, der die Tabelle Products beschreibt. Im Kontextmenü des TableAdapters finden Sie den Befehl Abfrage hinzufügen, den Sie anklicken. Im sich daraufhin öffnenden Assistenten haben Sie die Wahl, eine SQL-Anweisung zu verwenden, eine gespeicherte Prozedur zu erstellen oder eine vorhandene gespeicherte Prozedur zu verwenden. Wählen Sie die erstgenannte Option.
Im nächsten Schritt geben Sie den Abfragetyp an. Da uns die Produkte einer bestimmten Kategorie interessieren, entscheiden wir uns für die SELECT-Anweisung, die Zeilen zurückgibt (siehe Abbildung 36.13).
Abbildung 36.13 Festlegen des Abfragetyps
Bestätigen Sie mit Weiter, wird die Basisabfrage des TableAdapters angezeigt. Diese können Sie gemäß Ihren Anforderungen erweitern. Tragen Sie also in das Fenster des Assistenten
WHERE CategoryID = @CategoryID
ein, oder benutzen Sie alternativ dazu wieder den Abfrage-Generator.
Abbildung 36.14 Ergänzung der Basisabfrage des »TableAdapters«
Auf der folgenden Seite (siehe Abbildung 36.15) werden Bezeichner für die beiden Methoden GetData und Fill der neuen Abfrage verlangt. Die Vorschläge lauten FillBy und GetDataBy. Geben Sie den beiden Methoden sprechende Namen, beispielsweise FillByCategoryID und GetDataByCategoryID. Nach diesem Schritt ist die dem TableAdapter hinzugefügte Abfrage fertig.
Abbildung 36.15 Die Methoden »Fill« und »GetData« umbenennen
Das Ergebnis sehen Sie anschließend im Designer. Es liegen jetzt zwei parametrisierte Abfragen vor, denen wir CategoryID als Argument übergeben müssen.
Abbildung 36.16 Der TableAdapter mit einer hinzugefügten Abfrage
Testen wir zuerst die Methode FillByCategoryID. Hierzu benötigen wir zuerst eine Instanz des typisierten DataSets sowie eine Instanz des TableAdapters. Liegen beide Objekte vor, kann die Methode FillByCategoryID des TableAdapter-Objekts aufgerufen werden. Dabei teilen wir dem ersten Parameter mit, welche Tabelle des typisierten DataSets gefüllt werden soll, und geben im zweiten Parameter schließlich die Kategorienummer der auszugebenden Produkte an.
NWDataSet ds = new NWDataSet();
ProductsTableAdapter productsTA = new ProductsTableAdapter();
productsTA.FillByCategoryID(ds.Products, 5);
foreach (NWDataSet.ProductsRow row in ds.Products)
Console.WriteLine(row.ProductName);
Listing 36.9 Testen der Methode »FillByCategoryID«
Die Methode GetData erfordert etwas weniger Code, weil bekannterweise auf die Instanz des typisierten DataSets verzichtet werden kann. Auch diese Methode erwartet die Kategorienummer.
ProductsTableAdapter productsTA = new ProductsTableAdapter();
NWDataSet.ProductsDataTable tbl = productsTA.GetDataByCategoryID(5);
foreach (NWDataSet.ProductsRow row in tbl)
Console.WriteLine(row.ProductName);
Listing 36.10 Testen der Methode »GetDataByCategoryID«
36.5.7 Änderungen an einem »TableAdapter« vornehmen
Wenn Sie den TableAdapter im Designer markieren, werden dessen Eigenschaften im Eigenschaftsfenster von Visual Studio angezeigt. 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.
Abbildung 36.17 Das Eigenschaftsfenster eines »TableAdapters«
Besonders interessant sind die Manipulationsmöglichkeiten, die sich hinter den Eigenschaften DeleteCommand und UpdateCommand verbergen. Wie Sie wissen, sind die in der WHERE-Klausel aufgeführten Spalten entscheidend dafür, wann eine Änderung zu einem möglichen Konflikt führt. Möchten Sie ein vom Standard abweichendes Konfliktszenario realisieren, können Sie über das Eigenschaftsfenster manuell Einfluss darauf nehmen.
In der Klasse des typisierten DataSets können Sie bei Bedarf Eigenschaften und Methoden hinzufügen. Gleiches gilt natürlich auch für die Klasse des 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.