## 28.4 Der TableAdapter TableAdapter-Objekte habe ich in den Beispielen der letzten Seite 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 des Visual Studios erzeugt und nimmt eine besondere Stellung im Zusammenhang mit dem typisierten DataSet ein. Seine besondere Stellung können Sie auch schon 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 zum Beispiel 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. ### 28.4.1 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 DataSet auch eine TableAdapter-Klasse erzeugt – und zwar für jede Tabelle des typisierten DataSet genau ein TableAdapter. Dieses Verfahren habe ich Ihnen am Anfang des Kapitels bereits gezeigt. Ein TableAdapter lässt sich aber auch über Visual Studio 2010 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 28.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. Nachdem Sie die Verbindung eingerichtet haben, verlangt der Assistent im nächsten Schritt die Angabe des Befehlstyps (siehe Abbildung 28.7). 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. Hier klicken, um das Bild zu vergrößern Abbildung 28.6 Dialog zum Konfigurieren der Verbindung eines »TableAdapters« 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 (siehe Abbildung 28.8) geben Sie das SQL-Statement ein. Sollten Sie aber weiterhin dem Assistenten vertrauen, können Sie auch auf die Schaltfläche Abfrage-Generator klicken. Hier klicken, um das Bild zu vergrößern Abbildung 28.7 Festlegen des Befehlstyps Hier klicken, um das Bild zu vergrößern Abbildung 28.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 (siehe Abbildung 28.9). 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. Hier klicken, um das Bild zu vergrößern Abbildung 28.9 Die Tabelle für den »TableAdapter« festlegen Nachdem Sie das Dialogfenster geschlossen haben, können Sie im nächsten Fenster das SQL-Statement spezifizieren (siehe Abbildung 28.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. Entspricht es Ihren Erwartungen, schließen Sie das Fenster mit OK. Nun gelangen Sie wieder zu dem Dialogfenster zurück, das Sie in Abbildung 28.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 28.11 zu sehen. Hier klicken, um das Bild zu vergrößern Abbildung 28.10 Abfrage-Generator zum Erstellen einer Abfrage Hier klicken, um das Bild zu vergrößern Abbildung 28.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 DELETE verwendet. Die dritte und letzte Option, Datentabelle aktualisieren, ist nur dann verfügbar, wenn die abgefragte Datenbank Batch-Abfragen 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 28.12). Hier klicken, um das Bild zu vergrößern Abbildung 28.12 Methoden des TableAdapters festlegen 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 28.12 gezeigten Dialogs. ### 28.4.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 DataSet 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 DataSet
    // 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. ### 28.4.3 Die Methode »GetData« Ähnlich wie Fill arbeitet auch die Methode GetData. Allerdings müssen Sie sich vorher keine Instanz des typisierten DataSet 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);
### 28.4.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. ### 28.4.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 DataSet 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. ### 28.4.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 DataSet, 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ück gibt (siehe Abbildung 28.13). Bestätigen Sie mit Weiter, wird die Basisabfrage des TableAdapters angezeigt. Diese können Sie gemäß Ihren Anforderungen erweitern. Tragen Sie also
    WHERE CategoryID = @CategoryID
in das Fenster des Assistenten ein, oder benutzen Sie alternativ dazu wieder den Abfrage-Generator. Hier klicken, um das Bild zu vergrößern Abbildung 28.13 Festlegen des Abfragetyps Hier klicken, um das Bild zu vergrößern Abbildung 28.14 Ergänzung der Basisabfrage des TableAdapters Auf der folgenden Seite (siehe Abbildung 28.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. Hier klicken, um das Bild zu vergrößern Abbildung 28.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. Hier klicken, um das Bild zu vergrößern Abbildung 28.16 TableAdapter mit einer hinzugefügten Abfrage Testen wir zuerst die Methode FillByCategoryID. Hierzu benötigen wir zuerst eine Instanz des typisierten DataSet sowie eine Instanz des TableAdapter. Liegen beide Objekte vor, kann die Methode FillByCategoryID des TableAdapter-Objekts aufgerufen werden. Dabei teilen wir dem ersten Parameter mit, welche Tabelle des typisierten DataSet 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);
Die Methode GetData erfordert etwas weniger Code, weil bekannterweise auf die Instanz des typisierten DataSet 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);
### 28.4.7 Änderungen an einem TableAdapter vornehmen Wenn Sie den TableAdapter im Designer markieren, werden dessen Eigenschaften im Eigenschaftsfenster von Visual Studio angezeigt (siehe Abbildung 28.17). 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. Hier klicken, um das Bild zu vergrößern Abbildung 28.17 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.