Stefan Fellner Visual Basic .NET und Datenbanken Inhalt Einleitung 13 Ein Neubeginn mit Visual Basic .NET Update oder Neuschöpfung? 14 14 Die neue Datenbanktechnologie ADO.NET Es war einmal ein Recordset 17 Die Verbindung zur Datenbank 18 Ein erstes Beispiel 18 Tabelle laden und darstellen 19 Verknüpfte Tabellen und XML-Daten 17 21 Dieses Buch 25 Die Beispiele dieses Buches 29 Konventionen und Symbole 30 Tipp 33 1 Visual Studio .NET 1.1 1.1.1 1.1.2 1.1.3 1.1.4 Visual Studio .NET und das .NET Framework 36 Systemvoraussetzungen 37 Koexistenz mit Visual Basic 6, Upgrade von Projekten SQL Server und MSDE 38 IIS-Webserver 39 IIS nachträglich für das .NET Framework einrichten 40 1.2 1.2.1 Die Installation von Visual Studio .NET MSDE installieren 42 1.3 Die Entwicklungsumgebung von Visual Studio 43 Einschränkungen der MSDE aufheben 43 Was bietet die neue IDE? 45 Der Server-Explorer 49 Systemressourcen einbinden 50 Download von zusätzlichen Datenprovidern 50 .NET Framework-Zusätze verwenden 53 1.3.1 1.3.2 1.3.3 1.3.4 35 41 1.4 Dokumentation und Selbstdokumentation XML-Kommentare verwalten 56 1.5 .NET-Anwendungen debuggen 1.6 .NET-Anwendungen kompilieren 59 Debug und Release konfigurieren 59 Konfigurationseinstellungen 60 Kompilierreihenfolge 60 Vorkompilieren 61 Kommandozeilencompiler 62 Anwendungen mit dem .NET Framework kompilieren 64 1.6.1 1.6.2 1.6.3 1.6.4 38 54 57 Inhalt 5 1.7 1.7.1 1.7.2 Alternativen zur Visual Studio .NET-IDE SharpDevelop 65 Webmatrix 66 1.8 1.8.1 1.8.2 1.8.3 1.8.4 .NET-Anwendungen installieren 67 Zielordner festlegen 68 Setup-Gestaltung 68 Installation auf Nicht-.NET-Systemen 69 Benutzerdefinierte Aktionen 69 Benutzereingaben während einer Installation auswerten 70 1.9 1.9.1 1.9.2 Installieren der Beispieldatenbank 71 SQL Server-Datenbank installieren 72 MySQL-Datenbank installieren 75 MySQL-Datenbanken und Berechtigungen 75 1.10 1.10.1 1.10.2 1.10.3 1.10.4 Datenbankprojekte 77 Datenbankverweise 77 Skripte und Server-Explorer 79 Tabellen und Sichten 80 Gespeicherte Prozeduren 81 Debugging im SQL Server zulassen 81 Trigger 83 Diagramme 84 Erstellungsskripte 85 Hinweis auf ORM-Diagramme 85 Befehlsdateien 86 Projektverwaltung 86 Skripte zur Datenbankadministration zusammenstellen 88 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9 6 65 2 Entwickeln mit .NET 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 Common Language Runtime 90 Common Language Infrastructure 92 Just in Time-Compiler 92 Sprachintegration 93 Common Type System 93 Debugging 93 Strukturierte Fehlerbehandlung 94 Speicherverwaltung, Garbage Collector Garbage Collector gezielt aufrufen 97 Interoperabilität mit COM 97 2.2 Metadaten in .NET 2.3 Namensräume und Basisklassen 2.4 2.4.1 Assemblies 100 Manifest 102 Programmatischer Zugriff auf Assembly-Informationen 103 Inhalt 89 95 98 99 2.5 2.5.1 2.5.2 2.5.3 Sicherheit 104 Sicherheit auf Code-Ebene 104 Assemblies definieren Sicherheitsbereiche Administrieren von Rechten 106 2.6 2.6.1 2.6.2 Versionierung 107 Versionierung mit COM 107 Versionierung mit Assemblies 108 Neuere Version des .NET Frameworks verwenden 111 3 .NET, Windows und XML-Webdienste (Web Services) 3.1 3.1.1 Windows und COM Notlösungen 118 3.2 3.2.1 Windows und .NET 119 Anforderungen an .NET 119 3.3 Interaktionen über das Internet 3.4 Ein Objektmodell für das Internet 122 3.5 XML-Webdienste (Web Services) 122 3.6 Programmierbares Internet 3.7 3.7.1 3.7.2 3.7.3 3.7.4 3.7.5 Microsofts .NET-Strategie .NET Framework 125 .NET-Server 126 Visual Studio .NET 127 .NET Services 128 Software als Service 129 3.8 3.8.1 3.8.2 3.8.3 Plattformunabhängiges .NET Rotor (FreeBSD) 130 Mono (Linux) 131 DotGNU (GNU/Linux) 132 3.9 3.9.1 3.9.2 Eine Infrastruktur für Webdienste 132 Web Service Description Language (WSDL) UDDI-Verzeichnisse 135 4 Grundlagen der Datenbankbearbeitung 4.1 4.1.1 Einführung in Datenbanken Datenmodelle 143 4.2 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 Entwurf relationaler Datenbanken 147 Datentabellen und Datenschlüssel 147 Tabellenverknüpfungen 148 Benennungsregeln 149 Referentielle Integrität 150 Tabellen-Indizes 151 Normalformen 152 105 115 117 121 124 124 129 133 137 140 Inhalt 7 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.3.10 4.4 4.4.1 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.4.10 4.4.11 4.4.12 XML-Verarbeitung 184 Die Herkunft von XML 184 Datenabfragen in XML 186 XML-Dokumente 187 XSL-Transformationen 191 XML-Elemente und Attribute 193 XML-Dateien und XSD-Schemas 194 XML und Namensräume 200 XPath-Abfragen 201 XPath-Beispiele 205 XPath-Richtungsnavigation 213 XPath-Abfragen über URL 222 XQuery 222 4.5 SQL Server-Datenbanken anlegen und verwalten 228 Verbindungen zum SQL Server 229 Datenbank anlegen 230 SQL Server zum SQL Server Enterprise Manager hinzufügen 233 Transact-SQL verwenden 235 Datenbank aktivieren/deaktivieren 236 Datenbanken sichern und wiederherstellen 237 Datenbank dbWürzen aus Datenbanksicherung erstellen 240 Benutzer- und Rechteverwaltung 240 Berechtigungen für Datenbankrollen 247 Tabellen anlegen 249 Indizes anlegen 251 Einschränkungen 252 Referentielle Integrität 253 Diagramme verwenden 254 Trigger 255 Gespeicherte Prozeduren 257 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.5.6 4.5.7 4.5.8 4.5.9 4.5.10 4.5.11 4.5.12 8 SQL-Grundlagen 156 Structured Query Language 157 OSQL für SQL-Anweisungen verwenden 158 Schemainformationen 159 SELECT 161 Nulldatum des SQL Servers 169 UNION 174 JOIN 175 NULL-Werte 178 Vorgabewerte für Nicht-NULL-Felder 179 INSERT 180 UPDATE 181 DELETE 182 Ausblick 183 Duplikate in einer Tabelle finden 183 Inhalt 5 ADO.NET und Datenbanken 5.1 5.1.1 Einführung in ADO.NET 263 Wege nach ADO.NET 265 5.2 5.2.1 5.2.2 5.2.3 Verbindungen zur Datenbank 268 Universaler Datenzugriff 268 Das asynchrone Modell 269 Datenprovider 270 Datenprovider-unabhängiger Code 272 Connection-Objekt 274 Datenbankverbindungen in App.config konfigurieren 277 Transaktionen 279 Command-Objekt 281 Mit Gespeicherten Prozeduren arbeiten 283 Upgrade von ADO(alt) 285 Upgrade von Visual Basic 6 und Recordsets im DataSet 288 ODBC-Datenprovider 289 DataReader 290 DataReader und Recordset 291 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.2.10 5.3 263 5.3.1 5.3.2 Datengebundene Eingabemaske 296 Assistenten-Code verwenden 297 DataForm-Assistent 298 Das Projekt auf Code-Ebene 305 5.4 5.4.1 DataSet als lokale Datenbank 315 Transportable Datenbank 315 5.5 5.5.1 5.5.2 Datenadapter für das DataSet 317 DataAdapter 317 Datenadapter mit Assistenten konfigurieren 5.6 5.6.1 5.6.2 DataSet-Objekt 333 DataSet und XML 337 DataSet und XML-Dokument 5.7 5.7.1 5.7.2 5.7.3 Datenobjekte des DataSets DataTable 343 DataRow 353 DataView 356 5.8 5.8.1 5.8.2 5.8.3 Tabellenverknüpfungen 363 DataRelation 363 Darstellung verknüpfter Tabellen Zugriff auf Detaildaten 369 5.9 5.9.1 5.9.2 Datenauswahl und Datenaktualisierung CommandBuilder 371 Aktualisieren von Autowerten 373 5.10 5.10.1 5.10.2 Datenbindung und Datennavigation BindingContext 375 Datenmaske mit Bildern 378 Bilder aus der Datenbank 379 321 341 343 367 370 375 Inhalt 9 5.11 5.11.1 Hierarchische Daten 385 Datengebundenes Treeview 5.12 5.12.1 5.12.2 5.12.3 Asynchrone Datenbankanwendungen 402 Bedeutung von Transaktionen 402 Parallelitätssteuerung in ADO.NET 405 Parallelitätsverletzung behandeln 411 6 Datenzugriffsmodule 6.1 6.1.1 Datenbankzugriff und Analyse 418 Tabellen-Analyseprogramm 419 Datenbankverbindung mit DataLink-Dialog einrichten 425 Testmodul für Gespeicherte Prozeduren 438 6.1.2 6.2 6.2.1 6.2.2 6.2.3 6.2.4 7 386 417 Datenzugriffscode (Data Access Application Block) Aufbau des Data Access Application Blocks 446 Data Access Application Block einbinden 446 Aufruf mit SQL-Anweisung 449 Aufruf mit Gespeicherter Prozedur 453 Anwendungsbereich 458 445 Serverseitige Datenverarbeitung und Webdienste (Web Services) 461 Webdienstbeispiele installieren oder kopieren 463 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.1.6 7.1.7 7.2 7.2.1 7.2.2 10 Ein datengebundener Webdienst 465 Webprojekte auf entfernten Rechnern einrichten 466 Webdienst benennen 467 Namensräume hinzufügen 467 Verbindungen zur Datenbank 468 Pfad des virtuellen Verzeichnisses 469 Webdienstfunktionen 470 Datenbankzugriff für ASP.NET zulassen 472 Erstellen und Testen des Webdienstes 473 Webdienst mit Rückgabe eines DataSets 476 Webdienst mit DataSet testen 478 Testseiten in Produktivumgebung deaktivieren 479 7.2.3 7.2.4 7.2.5 Ein Webdienstclient 480 Dropdown Gewürzliste 480 Einbinden des Webdienstes 482 .vsdisco-Dateien verwenden 484 Dropdown an Daten aus dem Webdienst binden 486 Webdienst zur Aktualisierung der Datenbank 488 Webdienstclient zur Aktualisierung der Datenbank 491 7.3 7.3.1 Daten zwischen Anwendungsebenen übertragen Diffgram 500 Inhalt 499 7.4 7.4.1 7.4.2 7.4.3 7.4.4 XML und SQL Server 2000 506 OpenXML 508 OpenXML und ADO.NET 514 FOR XML-Abfragen 515 FOR XML im Query Analyzer 516 FOR XML und ADO.NET 530 7.5 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 Die SQLXML-Erweiterung 534 Einrichten von SQLXML 534 URL-Aufrufe 537 Abfragen an das Datenbankobjekt 538 Template-Abfragen 539 Schema-Abfragen 542 Webdienste aus dem SQL Server 543 Ausblick 545 7.6 7.6.1 7.6.2 Datenaustausch mit SQLXML Diffgrams als Templates 545 Zusammenfassung 548 8 Datenbankgestützte Webanwendungen 8.1 Editierbare Listenansicht einer Datenbanktabelle 551 Hinweise zur Einrichtung von ASP.NET-Projekten 551 Startseite benennen 552 Datenadapter konfigurieren 552 Variante mit Access-Datenbank 552 DataGrid hinzufügen und konfigurieren 554 Seite laden und Daten anzeigen 557 Datenbankzugriff für das Konto ASPNET 559 Paging: Datenausgabe in Ergebnisblöcken 561 Zwischenspeichern des DataSets im Session-Objekt 562 Daten sortieren 564 Datenbindung aus Code neu zuweisen 566 Daten bearbeiten, speichern, löschen 567 Daten hinzufügen 571 Datenbearbeitung über Login schützen 574 8.1.1 8.1.2 8.1.3 8.1.4 8.1.5 8.1.6 8.1.7 8.1.8 8.1.9 8.1.10 8.2 8.2.1 545 8.2.2 8.2.3 8.2.4 Datenbankgestützte Loginseite 574 Einfache Loginprüfung 575 Authentication und Authorization verwenden 576 Loginprüfung mit Datenbankabfrage 577 Login aufrufen 580 Login auswerten 581 A Links 585 Index 593 549 Inhalt 11 Einleitung Mit der Neufassung von Visual Studio auf Basis des .NET Frameworks ist Visual Basic .NET eine echte objektorientierte Sprache geworden, die es in Interaktion mit den ADO.NET-Datenobjekten sehr leicht macht, asynchrone Datenbankanwendungen, XML-Webdienste (Web Services) und datenbasierte ASP.NET-Webanwendungen zu entwickeln. Der Produktname Visual Basic .NET suggeriert eine Erweiterung oder einen Versionsschritt der weit verbreiteten Programmiersprache Visual Basic. Der Zusatz .NET lässt – wie bei anderen Produkten auch – eine starke Ausrichtung auf das Internet vermuten. Auch ADO.NET klingt nach einer speziellen Datenzugriffstechnik für vernetzte Lösungen. Der Titel dieses Buches, Visual Basic .NET und Datenbanken, verspricht Anleitungen, Grundlageninformationen und Beispielcode zum Datenbankzugriff speziell mit Visual Basic. Wer bereits Visual Basic 6, ADO und MDAC verwendet hat, wird darin eine Darstellung der neuesten ActiveX-Datenobjekte und siebzehn neue Techniken zur Verwendung des Recordsets erwarten. Um es vorwegzunehmen: Keine dieser Vermutungen trifft wirklich zu, denn weder ist Visual Basic .NET eine Fortschreibung von Visual Basic 6, noch beschränkt oder konzentriert sich die Verwendung des namengebenden .NET-Frameworks auf das Internet, und die in diesem Buch beschriebene Datenbanktechnologie ADO.NET wird auch mit anderen Programmiersprachen verwendet. Und ganz am Rande: Das Recordset ist abgeschafft. Die Gemeinsamkeiten von Visual Basic .NET und Visual Basic 6 sind eine im Wesentlichen übereinstimmende Sprachsyntax, der Name und das Konzept einer benutzerfreundlichen und produktiven Arbeitsumgebung. Das ist genug, um sich als Visual Basic 6-Entwicklerin oder -Entwickler schnell zurechtzufinden, und zu wenig, um ohne Hilfestellungen mit Visual Basic .NET die produktive Arbeit sofort fortzusetzen. Einleitung 13 Ein Neubeginn mit Visual Basic .NET Mit Visual Basic .NET beginnt für VB-Programmierer ein neues Zeitalter, in dem sie sich mit den Basisklassen des umfangreichen .NET Frameworks auseinandersetzen, die ADO.NET-Datenobjekte kennen lernen und die Möglichkeiten echter Objektorientierung sehr bald schätzen lernen werden. Der Neubeginn erfordert es allerdings, Lösungsansätze und Strategien für Datenbankanwendungen zu überdenken und auf der breiten Basis des .NET Frameworks neu zu konzipieren. Neueinsteiger haben es hier fraglos einfacher, die neue Plattform unbelastet zu entdecken. Wer eine kontinuierliche Fortsetzung der Geschichte von Visual Basic erwartet hat, sieht sich vermutlich getäuscht und um den Wert seines mit Visual Basic 6 erlernten Wissens gebracht. Der Aufwand, Schritt zu halten, ist nicht zu unterschätzen, denn ohne Basis-Kenntnisse in objektorientierter Programmierung und ohne ein Verständnis der Struktur und Funktionsweise des .NET Frameworks dürfte es schwer sein, erfolgreiche Anwendungen mit Visual Basic .NET zu erstellen. Die Lösung lautet auch hier: learn it now or be left behind. Update oder Neuschöpfung? VB6-Entwickler stehen damit vor dem Problem, dass sich existierender Datenbankcode nur in den seltensten Fällen sinnvoll nach Visual Basic .NET übertragen lässt. In der Regel wird es angebracht sein, die datenbankrelevanten Teile der Anwendung ganz neu zu entwickeln, um von den neuen Eigenschaften zu profitieren. Einige Anwendungsarten, z.B. jene, die einen ständig aktualisierten Datenstrom benötigen, oder solche, bei denen ein serverseitiger Cursor erforderlich ist, lassen sich nicht sinnvoll nach ADO.NET übertragen und sollten weiter in VB6 entwickelt und gepflegt werden. Visual Basic .NET ist eine neue Sprache Visual Basic .NET stellt kein einfaches Update auf eine neue Version dar, sondern ist vielmehr eine vollständige, objektorientierte Neufassung einer leicht zu erlernenden Programmiersprache für die .NETPlattform, die – überspitzt formuliert – so entwickelt wurde, dass sich Visual Basic 6-Programmierer leicht darin zurechtfinden können. Sie 14 Einleitung werden einerseits große Teile der ihnen gewohnten Syntax wieder finden und sofort anwenden können, andererseits erfordert .NET das Erlernen neuer Strukturen und neuer Anwendungskonzepte. Die Unterschiede zur Vorversion sind nicht mehr mit dem Begriff »Neuerungen« zu beschreiben, denn sie greifen tief in die Fundamente sowohl des Betriebssystems als auch der Programmiersprache(n) und der Struktur der damit zu erstellenden Anwendungen ein. Die Rolle der .NET-Plattform Die Einführung der .NET-Plattform, die u.a. ein umfassendes »Framework« von Basisklassen und Funktionen als Basis aller Programmiersprachen ist, die Microsoft mit dem Visual Studio anbietet, stellt einen Bruch mit den zuvor in Visual Basic favorisierten Technologien COM und ActiveX dar. Die .NET-Plattform besteht aus einer sehr großen Basisklassenbibliothek, einer Sammlung von Compilern für verschiedene Sprachen zur Erzeugung eines »Zwischencodes« und einem Laufzeitcompiler, der diesen Zwischencode zur Laufzeit in Maschinencode umsetzt. Diese Struktur eröffnet mehrere Möglichkeiten: Alle Programmiersprachen, die für das .NET Framework verfügbar sind, benutzen dieselbe Basisklassenbibliothek und haben damit theoretisch die gleichen Möglichkeiten und das gleiche Leistungsbild. Auch gemischtsprachige Projekte werden damit möglich. Da die Basisklassenbibliothek einem streng hierarchischen Benennungsschema folgt, kommen für sie erstellte Programme ohne eine Registrierung aus. Statt mit klassischen Installationen können .NET-Anwendungen auch nur durch einfaches Kopieren der Dateien ausgeliefert werden. Da sie nur das .NET Framework benötigen und erst zur Laufzeit in Code für die vorhandene Hardware umgesetzt werden, werden .NET-Anwendungen mit dessen (fortgeschrittener) Portierung auf andere Systeme im Prinzip plattformunabhängig und hardwareunabhängig funktionieren. Plattformübergreifende Interaktion Es wäre ein Missverständnis, beim Namen .NET vor allem eine Ausrichtung auf Internetanwendungen zu vermuten. Wesentliche und direkte Vorteile bringen .NET-Lösungen auch lokal für eine objektorientierte, konsistente Anwendungsentwicklung. Allein schon die überfällige Ablösung von den nur schwer zu wartenden COM-Objekten, Ein Neubeginn mit Visual Basic .NET 15 der zwingenden Verwendung der Windows-Registry und den Versionisierungsproblemen gemeinsam genutzter Systembibliotheken hätte den Aufwand einer neuen Programmplattform gerechtfertigt. Tatsächlich sind aber auch Internetanwendungen und XML-Webdienste, die programmatischen Zugriff auf Logik oder Daten sicher über das Internet ermöglichen, ein wesentlicher Bestandteil von .NET. In der Verbindung mit einer durchgehenden Integration von XMLTechnologien lassen sich beliebige Objekte serialisieren und mit dem SOAP-Protokoll zwischen Anwendungen und Webdiensten übertragen und austauschen. Bücher zu Visual Basic .NET Man kann die Bücher, die bereits zu Visual Basic .NET erschienen sind, in drei Gruppen einteilen. Zur ersten gehören Werke, die den Umstieg von Visual Basic 6 erleichtern möchten und zeigen, dass man in Visual Basic .NET auch noch in starker Anlehnung an die Methoden von Visual Basic 6 programmieren kann. Dabei wird nur soviel von der Objektorientierung und dem .NET Framework erklärt, wie es unbedingt erforderlich scheint. Zur zweiten Gruppe gehören Grundlagenwerke und zukünftige Referenztitel, in denen objektorientiertes Programmieren von Grund auf erschlossen wird. Diesen Büchern ist gemeinsam, dass sie, um die Leser auf das Niveau der Anwendungsentwicklung zu führen, auf dem sie sich mit Visual Basic 6 befunden haben, mindestens den doppelten Umfang eines vergleichbaren Werkes zu Visual Basic 6 haben müssen. In der dritten Gruppe, zu der auch dieses Buch gehört, werden ausgewählte Sonderthemen behandelt, die wie die Datenbankentwicklung zum Teil dramatischen Veränderungen unterworfen sind. Gleichzeitig spricht diese Gruppe von Büchern Leserinnen und Leser mit sehr unterschiedlichem Vorwissen an, so dass es notwendig ist, darin die Darstellung von Grundlagen und Anwendungskonzepten mit Beispielen und praktischen Hinweisen in ein ausgewogenes Verhältnis zu bringen. Verkürzt gesagt: Ein einzelnes Buch wird nicht ausreichen, um Visual Basic .NET umfassend zu erschließen. Wenn Sie von Visual Basic 6 umsteigen, empfiehlt sich die Zuhilfenahme eines Werkes aus der zweiten Gruppe. 16 Einleitung Die neue Datenbanktechnologie ADO.NET Microsofts Marketingabteilung hat mit dem Namen ADO.NET für die Datenbanktechnologie in Visual Studio.NET eine zweifelhafte Wahl getroffen. ADO stand in Visual Basic 6 für ActiveX Data Objects, die auf der Component Object Model (COM)-Architektur basieren. ADO.NET hat weder etwas mit ActiveX-Technologie noch mit COM zu tun und wäre mit seiner Nähe zu XML vielleicht nach William R. Vaughn ein Kandidat für die Bezeichnung XDO gewesen. Um Verwechslungen zu vermeiden, werden in diesem Buch deshalb ActiveX Data Objects als ADO(alt), im Gegensatz zu ADO.NET, bezeichnet. ADO(alt) Es war einmal ein Recordset Eine der grundlegenden Änderungen von ADO(alt) zu ADO.NET ist der Wegfall der universellen Tabelle Recordset zu Gunsten des universellen Datencontainers DataSet, der mehrere Tabellen und ihre Verknüpfungen aufnehmen kann und alle Qualitäten einer transportablen Datenbank hat. DataSet Auch wenn das ersatzlose Streichen der zentralen Datenbearbeitungstechnik aus Visual Basic 6 eine drastische Maßnahme ist, die eine Neuentwicklung aller vorhandenen Datenzugriffsmodule erfordert, lohnt sich der Aufwand, wie Sie bei der Lektüre dieses Buches sicher feststellen werden. Das Arbeiten mit DataSets heißt zum einen, dass der gesamte Aufwand entfällt, der notwendig ist, um Daten aus mehreren Datenbanktabellen zuerst in einem Recordset zusammenzufassen, um sie danach in der Anwendung wieder aufzuschlüsseln und über ein anderes Recordset gezielt zu aktualisieren. Zum anderen lässt sich ein DataSet als eine Kopie oder ein Teilauszug der originalen Datenbank in der Anwendung verstehen. ADO.NET verwendet Hilfsobjekte wie z.B. Datenadapter, die für den Transport der Daten aus der Datenbank in das DataSet und zurück sorgen. Folgerichtig ist nur noch dann eine Verbindung zur Datenbank notwendig, wenn Daten geladen oder aktualisiert werden. Die neue Datenbanktechnologie ADO.NET 17 Die Verbindung zur Datenbank asynchron Womit die zweite, fundamentale Änderung genannt wäre: ADO.NETAnwendungen verwenden eine Datenbank asynchron, d.h., sie sind nur noch für den möglichst kurzen Zeitraum, den eine Lese- oder Schreibaktion dauern sollte, mit der Datenbank verbunden. In ADO.NET-Anwendungen werden, anders als in ADO(alt), immer erst alle zu verarbeitenden Daten in das DataSet der Clientanwendung geladen, wo die Datenverarbeitung vollständig getrennt von der Datenbank stattfindet. Die Entsprechung in ADO(alt) wäre ein clientseitiger Cursor, dem in der Regel zur Steigerung der Leistungsfähigkeit ein serverseitiger Cursor vorgezogen wird, um möglichst wenig Daten zum Client zu übertragen und möglichst viel Datenbanklogik vom Datenbankserver ausführen zu lassen. Mit dem asynchronen Modell von ADO.NET entfällt diese Option, und der Einsatz der klassischen serverseitigen Datenverarbeitung reduziert sich auf die Datenbereitstellung und das Ausführen von Gespeicherten Prozeduren. Ein einmal gefülltes DataSet behält auch, nachdem seine Daten in einer Anwendung bearbeitet wurden, immer einen Bezug zu seinem Originalzustand nach dem Laden der Daten. Alle Änderungen, Hinzufügungen und Löschungen werden gekennzeichnet und erlauben es, zu einem beliebigen späteren Zeitpunkt ausschließlich die Veränderungen in die Datenbank zu übertragen. In diesem Moment lässt sich sogar feststellen, ob ein anderer Client dieselben Daten ebenfalls verändert hat, um beide Anwender über die Datenkonkurrenz zu informieren. Ein erstes Beispiel Im Folgenden soll ein kleines Beispiel einen ersten Eindruck von den Objekten und Methoden vermitteln, die für einen asynchronen Datenbankzugriff mit ADO.NET verwendet werden. Dabei werden mehrere Datenbanktabellen in der Listenansicht eines DataGrids dargestellt. 18 Einleitung Tabelle laden und darstellen Das folgende Beispiel finden Sie auf der beiliegenden CD-ROM im Verzeichnis \Einleitung\dbEinleitung. Legen Sie in Visual Studio .NET ein Projekt vom Typ WindowsAnwendung an, das Sie dbEinleitung nennen. Ziehen Sie in der Entwurfsansicht der Maske Form1 aus dem Abschnitt Windows Forms der Toolbox ein DataGrid auf die Maske. Beispiel dbEinleitung Wechseln Sie über das Menü Ansicht • Code in die Code-Ansicht. Wählen Sie aus dem linken Codenavigations-Dropdown Basisklassenereignisse, dann aus dem rechten Load, um die Ereignisprozedur für das Laden von Form1 anzulegen, in die der gesamte Code dieses Beispiels geschrieben wird. Das Beispiel verwendet die Access-Datenbank dbWürzen.mdb, die Sie auf der CD-ROM im Verzeichnis \Datenbank finden. Kopieren Sie diese Datei in das \bin-Unterverzeichnis des Projektverzeichnisses, das in der Voreinstellung den Pfad %userprofile%\Eigene Dateien\ Visual Studio-Projekte\dbEinleitung hat. Verbindung zur Datenbank Die Verbindung zur Datenbank wird über ein Connection-Objekt hergestellt, das dem gleichbenannten Objekt in Visual Basic 6 ähnlich ist und die gleiche Syntax für die Verbindungszeichenketten verwendet. Im Beispiel wird Access als OleDB-Datenbank angesprochen, weshalb die OleDb-Variante des Connection-Objektes erforderlich ist. Da die Datenbank im Projektausgabeverzeichnis \bin liegt, genügt die Angabe des Dateinamens in der Verbindungszeichenkette (siehe Listing 0.1). Datenadapter Das Füllen einer Datentabelle aus einer OleDbConnection erfordert ebenfalls die OleDB-Variante des Datenadapters. Der OleDbDataAdapter wird mit einer SQL-Anweisung für die Datenauswahl und ein angegebenes Connection-Objekt erstellt. Im Beispiel werden Daten aus der Tabelle tblGewürze ausgewählt. Ein erstes Beispiel 19 Datentabelle füllen Der Vorgang, die Daten zu laden, erfolgt allein durch den Aufruf der Fill-Methode des Datenadapters, die mit der Datentabelle als Argument aufgerufen wird. Daten anzeigen Die Datentabelle, die jetzt Daten enthält, wird dem DataGrid1 als DataSource-Eigenschaft zugewiesen. Als DataMember genügt der Name der Datentabelle, den das Objekt DataTable als Textwert trägt. Listing 0.1 Laden und Darstellen einer Tabelle Private Sub Form1_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load ' Die Verbindung zur Datenbank Dim _conn As New OleDb.OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=dbWürzen.mdb;" & _ "User ID=Admin;") ' Die Datenadapter Dim _daGewürze As New OleDb.OleDbDataAdapter( _ "SELECT pkGewürz," & _ " Gewürzname,Gattung " & _ "FROM tblGewürze", _ _conn) ' Eine Datentabelle erzeugen Dim _dt As New DataTable() ' DataSet über Datenadapter füllen _daGewürze.Fill(_dt) _conn.Close() ' DataSet im DataGrid anzeigen DataGrid1.DataSource= _dt DataGrid1.DataMember= _dt.ToString End Sub Mit dem Start der Anwendung über (F5) wird die Datentabelle über den Datenadapter gefüllt und im DataGrid angezeigt. 20 Einleitung Abbildung 0.1 Darstellung einer Datentabelle im DataGrid Der gezeigte Weg ist nicht aufwändiger als eine funktionsgleiche Lösung mit einem ADO(alt)-Recordset. Verknüpfte Tabellen und XML-Daten In einer erweiterten Version des Beispiels werden mehrere Tabellen in das DataSet übertragen und dort wie in einer Datenbank verknüpft. Diese Aufgabe liegt außerhalb der Fähigkeiten einer Recordset-Verarbeitung, ist in ADO.NET jedoch mit einer geringfügigen Erweiterung des Codes möglich. Definieren Sie einen zweiten Datenadapter mit einer weiteren SQLAnweisung, im Beispiel stammen die Daten aus der Tabelle tblBilder, die in der Datenbank mit der Tabelle tblGewürze über deren Primärschlüssel verknüpft ist. Tabellen im DataSet Ändern Sie den Aufruf der Fill-Methode dahin, dass anstelle der Datentabelle ein DataSet-Objekt gefüllt wird, das als zusätzliches Argument einen Namen für die zu erstellende Tabelle erwartet. Fügen Ein erstes Beispiel 21 Sie den Aufruf eines zweiten Datenadapters hinzu, dessen FillMethode auf das gleiche DataSet angewendet wird. Verknüpfung Das DataSet enthält an dieser Stelle zwei benannte Datentabellen, für die eine Verknüpfung wie in einer Datenbank definiert werden kann. Es beinhaltet zudem eine Auflistung der Relations genannten Tabellenverknüpfungen, der Sie durch Anwendung der Add-Methode ein neues DataRelation-Objekt für die beiden Tabellen hinzufügen können. Zur Erzeugung der Tabellenverknüpfung sind lediglich die zu verknüpfenden Spaltennamen, im Beispiel tblGewürze.pkGewürz und tblBilder.fkGewürz, sowie ein Name für die Verknüpfung erforderlich. Das DataGrid akzeptiert auch ein DataSet als DataSource-Eigenschaft, als DataMember wird einer der beiden Tabellennamen angegeben, die aus der Tables-Auflistung des DataSets verfügbar sind. XML-Daten Um die Verknüpfung der Daten in der Anzeige zu sehen, kann ein Textbox-Steuerelement verwendet werden, in dem die Daten des DataSets als XML-Daten ausgegeben werden. Ziehen Sie dazu in der Entwurfsansicht von Form1 eine Textbox aus der Toolbar auf die Maske. Setzen Sie im Eigenschaftenfenster die Eigenschaft Multiline auf True und positionieren Sie die Textbox1 neben dem DataGrid1 auf der Maske. Die XML-Daten werden in einem Schritt mit Hilfe der GetXmlMethode des DataSets in die Textbox übertragen. Fügen Sie vor der Anzeige der XML-Daten eine Zuweisung des Wertes True an die Nested-Eigenschaft der DataRelation im DataSet ein, um eine geschachtelte Darstellung der XML-Daten zu erhalten. Listing 0.2 Tabellen verknüpfen und XML-Daten anzeigen Private Sub Form1_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load 22 Einleitung ' Die Verbindung zur Datenbank Dim _conn As New OleDb.OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=dbWürzen.mdb;" & _ "User ID=Admin;") ' Die Datenadapter Dim _daGewürze As New OleDb.OleDbDataAdapter( _ "SELECT pkGewürz," & _ " Gewürzname,Gattung " & _ "FROM tblGewürze", _ _conn) Dim _daBilder As New OleDb.OleDbDataAdapter( _ "SELECT fkGewürz," & _ " Bildname " & _ "FROM tblBilder", _ _conn) ' Das DataSet erzeugen Dim _ds As New DataSet() ' DataSet über Datenadapter füllen _daGewürze.Fill(_ds, "Gewürze") _daBilder.Fill(_ds, "Bilder") _conn.Close() ' Tabellenbeziehung einrichten _ds.Relations.Add( _ New DataRelation( _ "relGewürzeBilder", _ _ds.Tables("Gewürze").Columns("pkGewürz"), _ _ds.Tables("Bilder").Columns("fkGewürz"))) ' Tabellenbeziehung geschachtelt darstellen _ds.Relations.Item(0).Nested = True ' DataSet im DataGrid anzeigen DataGrid1.DataSource= _ds DataGrid1.DataMember= _ds.Tables("Gewürze").ToString ' DataSet als XML anzeigen TextBox1.Text = _ds.GetXml End Sub Starten Sie die Anwendung erneut. Sie sehen neben der Datentabelle Gewürze in der Textbox den XML-Code des dazugehörigen DataSets angezeigt, der in jedem Abschnitt Gewürze den entsprechenden Abschnitt aus Bilder enthält. Ein erstes Beispiel 23 In der Tabellenansicht sehen Sie im Kopf jeder Zeile einen Datenknoten, der beim Klick einen Link auf die in der Tabelle Bilder verknüpften Daten freigibt. Folgen Sie diesem Link, wird der entsprechende Datensatz der Tabelle Bilder im DataGrid angezeigt. Abbildung 0.2 Darstellung verknüpfter Tabellen im DataGrid und als XML-Daten- menge Der Aufwand, der in diesem Beispiel notwendig war, um Tabellen zu übertragen, sie zu verknüpfen und in verschiedenen Modi auszugeben, ist minimal und erfordert keinerlei Datenschleifen und keine direkte Auswertung der Tabelleninhalte. Die gesamte Darstellung der Inhalte findet statt, nachdem die Verbindung zur Datenbank schon wieder geschlossen ist. Nach einer Veränderung der Daten im DataSet könnte ein Aktualisieren der Datenbank, für jede Tabelle getrennt, allein über den Aufruf der Update-Methode des Datenadapters erfolgen. 24 Einleitung Dieses Buch Dieses Buch ist in der Absicht geschrieben, den Einstieg in die Datenbankprogrammierung mit Visual Basic .NET zu erleichtern und über die Hürden und Fallstricke hinweg zu helfen, die der Autor bei der Beschäftigung mit Visual Studio .NET seit der Beta 1 selbst erfahren hat. Anhand von praktischen Beispielen und wieder verwendbarem Code soll es Sie in die Programmierung von Datenbankanwendungen mit Visual Basic .NET einführen. Sie lernen die Leistungsfähigkeit und Vorteile der neuen Datenbankobjekte in ADO.NET und ihre Bedeutung für neue Anwendungskonzepte kennen und erfahren Konzepte und Techniken, wie Sie mit Visual Basic .NET Datenbanken nutzen, erstellen, ändern, Daten darstellen, bearbeiten und austauschen. Kapitel 1 stellt die neue Entwicklungsumgebung von Visual Studio .NET vor. 왘 Es werden Hinweise zur Installation und Einrichtung von Visual Studio .NET gegeben sowie einige zusätzliche Datenprovider für ADO.NET vorgestellt, die durch Download ergänzt werden können. 왘 Es gibt einen Überblick über die wichtigsten Bestandteile und Funktionen zum Erstellen, Debuggen, Verwalten und Ausliefern von Anwendungen für das .NET Framework. 왘 Die Einrichtung der Desktop-Version von SQL Server 2000 (MSDE) und des IIS-Webservers wird beschrieben sowie die manuelle Installation der Beispieldatenbank dbWürzen auf einem SQL Server und in einer MySQL-Datenbank. Kapitel 2 stellt das neue Visual Basic in den größeren Zusammenhang des .NET Frameworks und erklärt seine Bestandteile. 왘 Sie erfahren, was die Common Language Runtime und was eine Assembly ist, wie Visual Basic .NET mit dem .NET Framework interagiert und wie Komponenten versioniert werden. 왘 Es werden Hinweise zum Sicherheitskonzept und der Speicherverwaltung des .NET Frameworks gegeben. Dieses Buch 25 Kapitel 3 zeigt den Zusammenhang von .NET Framework, XML-Webdiensten (Web Services) und dem programmierbaren Internet in der .NET-Strategie von Microsoft, um die Hintergründe der grundlegenden Neuerungen deutlich zu machen. 왘 Es erläutert, warum Microsoft entschieden hat, die COM-Technologie nicht weiter zu verfolgen und stattdessen das .NET Framework als Grundlage zukünftiger Windows-Versionen zu verwenden. 왘 Es wird gezeigt, was es mit dem Schlagwort vom programmierbaren Internet auf sich hat und welche Rolle XML-Webdienste in diesem Konzept spielen. 왘 Sie erfahren, wie mit Hilfe der Common Language Infrastructure .NET-Code auch auf anderen Plattformen kompiliert und ausgeführt werden kann und welche Initiativen es dazu gibt. Kapitel 4 stellt Grundlagen der Datenbankbearbeitung mit relationalen Datenbanken und hierarchischen Datenmengen in XML dar. 왘 Es wird der Entwurf relationaler Datenbanken unter der Berücksichtigung der klassischen Normalformen gezeigt. 왘 Ein eigener Abschnitt führt anhand von Beispielen in die Grundlagen von SQL-Anweisungen ein. 왘 Da mit ADO.NET neben dem klassischen SQL auch XML-Technologien bei der Entwicklung datenbankgestützter Anwendungen zum Einsatz kommen, wird erläutert, wie XML-Daten und XML-Datenschemas aufgebaut sind. Ihre Verarbeitung wird anhand der XPathNavigation und mit XQuery-Abfragen demonstriert. 왘 Die Darstellung der Grundlagen wird ergänzt durch Anleitungen zur Verwaltung und Administration von SQL Server-Datenbanken mit dem SQL Server Enterprise Manager und über SQL-Skripte. Kapitel 5 erklärt das objektbasierte Datenbankmodell von ADO.NET ausführlich anhand praxisnaher Aufgabenstellungen und Themen. 왘 26 Es wird ein Überblick über die Objekte gegeben, die in ADO.NET verwendet werden, und ihr Zusammenhang an einem vollständigen Beispiel einer datengebundenen Maske erläutert. Einleitung 왘 Das neue und zentrale Datenbankobjekt DataSet wird ausführlich erläutert, und seine Unterobjekte werden einzeln vorgestellt. Die Objekte werden durch Auflistungen ihrer Eigenschaften und Methoden direkt vergleichbar gemacht, um sie in Anwendungen gezielt einsetzen zu können. 왘 Es werden fortgeschrittene Themen wie die Darstellung hierarchischer Daten und die Verarbeitung von Bildern in der Datenbank behandelt. 왘 Es geht auch um die Aufgaben zur Aktualisierung und zur Behandlung von Datenkonkurrenz, die sich mit dem asynchronen Modell von ADO.NET neu stellen. Kapitel 6 stellt Codemodule vor, mit deren Hilfe Sie den Datenzugriff in ADO.NET-Anwendungen vereinfachen und vereinheitlichen können. 왘 Es erläutert Hilfsanwendungen zur Analyse von Datenbanktabellen und von Gespeicherten Prozeduren. 왘 Es gibt eine Einführung in den Data Access Application Block, mit dem Microsoft optimierten Datenzugriffscode als .NET-Komponente veröffentlicht hat. Kapitel 7 behandelt die serverseitige Datenverarbeitung verteilter Komponenten. 왘 Es wird gezeigt, wie ASP.NET-Webdienste zur Anzeige und zur Aktualisierung von Datenbankinhalten erstellt und genutzt werden. 왘 Es behandelt die XML-Verarbeitung im SQL Server 2000 und die Veröffentlichung von Webdiensten aus dem SQL Server. 왘 Der Datenaustausch zwischen den Ebenen einer Anwendung wird erläutert und das dabei verwendete Diffgram-Format des DataSets dargestellt. Dieses Buch 27 Kapitel 8 beschäftigt sich mit datenbankgestützten Webanwendungen. 왘 Es führt in die Anwendung der ADO.NET-Programmierung auf ASP.NET-Webanwendungen ein und gibt Hinweise zur Umsetzung von Webprojekten und verteilten Anwendungen. Das Buch ist somit in mehrere Teile untergliedert, die unterschiedlichen Leserbedürfnissen bzw. -perspektiven Rechnung tragen. Die Kapitel 1 bis 3 eignen sich als Einführung und Überblick über die Technologie und ermöglichen damit eine Einschätzung des Aufwands, von Visual Basic 6 auf Visual Basic .NET umzustellen. Kapitel 4 und 5 sind die Lernkapitel für Programmierer, in denen die Datenbankgrundlagen und die neue Datenbanktechnologie ADO.NET an vielen Beispielen vermittelt werden. Kapitel 6 vertieft die Thematik für fortgeschrittene Anwender, während Kapitel 7 und 8 erweiterte Projekte und verteilte Anwendungen darstellen. Systemvoraussetzungen und Installation Um die Beispiele in diesem Buch nachvollziehen zu können, muss mindestens das .NET Framework, besser natürlich Visual Studio .NET installiert werden. Da das .NET Framework den Compiler für Visual Basic .NET vbc.exe enthält, können damit vollständige Anwendungen allein aus dem Quellcode und den Beispielen dieses Buches kompiliert werden. Die Beispielprojekte in diesem Buch basieren auf dem Funktionsumfang der Professional-Ausgabe von Visual Studio .NET und funktionieren mit dem .NET Framework 1.0 mit Servicepack 2. Sie finden das .NET Framework und das .NET Framework SDK sowie das aktuelle Servicepack auf der beiliegenden CD-ROM im Verzeichnis \DotNETFramework. Bei der Installation von Visual Studio .NET oder des .NET Framework SDK haben Sie die Option, eine Desktopversion MSDE des SQL Server 2000 einzurichten, die als Datenbankserver für die Beispiele in diesem Buch völlig ausreicht. Bitte beachten Sie, dass die ausführbaren Anwendungsdateien der Beispielprojekte, die sich im Projektausgabeverzeichnis \bin einer Beispielanwendung befinden, bezüglich der installierten Version des .NET Frameworks versionsgebunden sind. Sie müssen nach Anwen- 28 Einleitung dung eines neueren Servicepacks oder einer Aktualisierung des Frameworks neu kompiliert werden. Die Beispiele dieses Buches Die Beispieldatenbank für die Beispiele in diesem Buch heißt dbWürzen und enthält eine Sammlung von Gewürzen mit Informationen zur Gattung, Herkunft und den Mischungen, in denen sie verwendet werden. Die Datenbank ist sowohl als SQL Server-Datenbank dbWürzen als auch als Access-Datenbank dbWürzen.mdb und als allgemeines SQL-Datenbankskript auf der CD-ROM im Verzeichnis \Datenbank zu finden. Die Einrichtung der Datenbank per Datenbankskript wird in Abschnitt 1.9, »Installieren der Beispieldatenbank«, beschrieben. Die meisten Beispiele in diesem Buch funktionieren sowohl mit der Access- als auch mit der SQL Server-Datenbank. Einige Beispiele sind jedoch ausschließlich mit einer SQL Server-Datenbank zu nutzen. Datenbank dbWürzen Auf der beiliegenden CD-ROM finden Sie im Verzeichnis \Installation drei Installationsprogramme, mit denen die Beispieldatenbank dbWürzen und die Beispiele dieses Buches, getrennt nach Windowsund Webanwendungen, einschließlich Quellcode installiert und bis auf die Datenbank auch wieder deinstalliert werden können. 왘 dbVBNETAnwendungen.msi Installiert die Windows-Beispielanwendungen aus den Kapiteln 1, 4, 5 und 6. Die Installation setzt voraus, dass zuvor Visual Studio .NET oder das .NET Framework sowie ein SQL Server oder eine MSDE installiert wurden. Der Datenbankserver sollte zum Zeitpunkt der Installation bereits laufen. 왘 dbVBNETWebAnwendungen.msi Installiert die Webdienste und Webanwendungen aus den Kapiteln 7 und 8. Die Installation setzt voraus, dass Sie einen lokalen IIS-Webserver und Visual Studio .NET oder das .NET Framework installiert haben. Das Installationsprogramm legt virtuelle Verzeichnisse für die Webdienstbeispiele aus Kapitel 7 und die Webanwendungsbeispiele aus Kapitel 8 an. Dieses Buch 29 왘 dbWürzenMSSQL.msi Installiert die SQL Server-Datenbank dbWürzen, die in den Beispielen verwendet wird. Während der Installation werden Sie nach dem Namen eines SQL Servers gefragt, auf den Sie Zugriff mit Integrierter Sicherheit haben. Der Servername eines SQL Servers auf einem Einzelrechner ist entweder (local) oder, wenn Sie eine MSDE benutzen, (local)\NetSDK oder (local)\VSdotNET. Startmenü Die Installationsprogramme fügen dem Startmenü unter Programme den Punkt VB.NET und Datenbanken hinzu, der nach Kapiteln sortierte Verknüpfungen zu den Visual Studio .NET-Beispielprojekten und zu den ausführbaren fertigen Beispielanwendungen enthält. Beispielcode Auf der beiliegenden CD-ROM finden Sie in den mit Kapitelnamen benannten Unterverzeichnissen die beschriebenen Beispielprojekte und alle Listings aus den Anleitungen innerhalb der einzelnen Kapitel. Die Beispielprojekte können auch ohne die Verwendung des Installationsprogrammes durch Kopieren der entsprechenden Verzeichnisse von der beiliegenden CD-ROM verwendet werden. Der dabei übertragene Schreibschutz sollte zurückgesetzt werden. Die Projekte sind vollständig und enthalten im Unterverzeichnis \bin jeweils die ausführbare *.exe-Datei, sofern es sich nicht um eines der Webprojekte handelt. Von der Startseite index.html der beiliegenden CD-ROM aus haben Sie direkten Zugriff auf alle Beispieldateien und Programmlistings. Eventuell notwendige Aktualisierungen finden Sie mit Ihrer persönlichen Registriernummer am Ende des Buches auf der Website von Galileo Press unter: http://www.galileocomputing.de. Konventionen und Symbole Programmcode und Listings Soweit es zum Verständnis notwendig ist, wird im Text der dazugehörige Programmcode dargestellt. An den Stellen, an denen satztechnisch ein Umbruch erforderlich ist, werden die Zeilenfortsetzungszeichen _ und & _ eingesetzt, die auch von Visual Basic .NET verwendet werden. Damit ist es unproblematisch, den Programmcode auch manuell zu übertragen. 30 Einleitung Abbildung 0.3 Zugriff auf Beispiele und Listings auf der CD-ROM dieses Buches Konventionen zur Objektbenennung In diesem Buch werden Objekte in Anlehnung an die Konventionen benannt, die Greg Reddick für Visual Basic-Programme aufgestellt hat. Einen Link zu den Reddick VBA Naming Conventions (RVBA) finden Sie in der Linkliste im Anhang. An derselben Stelle gibt es auch einen Verweis auf die Arbeitsversion der Reddick .NET Conventions für VB.NETObjekte. In der Absicht, Programme leichter lesbar zu machen, ermöglichen es diese Benennungsregeln, Objektnamen so aufzubauen, dass daran Informationen über die Art, den Geltungsbereich und die Herkunft eines Objektes ablesbar werden. Die Regeln folgen der so genannten Ungarischen Notation, nach der Objekte einheitlich benannt werden in der Form: [Präfix]Tag[BasisObjektName[Suffix]] Die BasisObjektNamen werden wie in Pascal mit großen Buchstaben am Anfang jeden Wortes geschrieben, wie z.B. in GewürzListe. Dieses Buch 31 Ergänzt werden diese Namen durch einen kurzen, vorangestellten Tag, der den Datentyp des Objektes angeben sollte, z.B. strGewürzName für eine Zeichenkette (String) oder intGewürz für einen IntegerWert. Die aus Tag und BasisObjektName zusammengesetzte Benennung erhält einen Präfix, der den Geltungsbereich des Objektes angibt: 왘 g für öffentliche, als public deklarierte, globale Variablen, z.B. gintGewürze 왘 m für modulweite, als private deklarierte, gültige Variablen, z.B. mstrGattung 왘 s für als static deklarierte Variablen, z.B. sintAufrufe Ohne Präfix bleiben nur prozedurweit gültige Objekte. In diesem Buch werden allgemeine, in ihrem Geltungsbereich auf Prozeduren beschränkte Objekte in der Regel nur mit dem Tag und dem Präfix _ benannt, z.B. _dt für eine Datentabelle. Geltungsbereich von Variablen Mit Visual Basic .NET werden neue, auf Prozedurabschnitte begrenzte Geltungsbereiche von Variablen eingeführt. Eine innerhalb einer Schleife oder einer strukturierten Fehlerbehandlung deklarierte Variable ist nur dort gültig. Dim arrNamen as String() Dim _str as String For each _str in arrNamen Dim _int as Integer _int = _str.Length Console.writeLine(_int) Next Die verborgene Variable _int belegt nur für die Dauer der Schleifenauswertung Ressourcen und steht außerhalb der Schleife nicht zur Verfügung. Dort kann allerdings auch keine weitere Variable mit dem Namen _int deklariert werden. Auf die fehlerhafte Verwendung verborgener Variablen werden Sie bereits zur Entwurfszeit von Visual Studio hingewiesen. 32 Einleitung Symbole Tipp In den Hauptkapiteln finden Sie am Ende wichtiger Abschnitte grau unterlegte Anleitungen, in denen praktische Hinweise und Anleitungen zur Konfiguration und Einrichtung von Visual Studio .NET und den verwendeten Serverdiensten gegeben werden. Hinweise warnen vor bekannten Missverständnissen und sollen der Vermeidung häufig vorkommender Probleme dienen. Ebenso kann es sich um Verweise auf weiterführende Informationen handeln. Dieses Buch 33 ' Einzelnen Datensatz anzeigen MessageBox.Show(dtGewürze.Rows.Item(12). _ Item("Gewürzname").ToString) End Sub Das XMLDocument enthält eine Eigenschaft DataSet, die eine relationale Darstellung seiner Inhalte ermöglicht. XML-Daten werden im XML-Dokument mit Hilfe ihres abgeleiteten Schemas ausgewertet, und können über das relationale Modell des DataSets z.B. als Datenzeilen angesprochen werden. Im Beispiel wird der Inhalt des DataSets erneut in XML gewandelt und in derTextbox angezeigt. Abbildung 5.29 XML-Daten aus einem synchronisierten DataSet 5.7 Datenobjekte des DataSets 5.7.1 DataTable Die DataTable ist ein einfaches Objekt, das eine Datenquelle darstellt. Es enthält keine Information über die Herkunft der Daten, mit denen es gefüllt ist. Eine DataTable kann alleine oder innerhalb eines DataSets verwendet werden. Datenobjekte des DataSets 343 Auslesen von Änderungen über GetChanges() DataTables überwachen wie DataSets Änderungen an den geladenen Daten bzw. sind Bestandteil der Änderungsverfolgung von DataSets. Geänderte Daten können über die Methode GetChanges als eigene DataTable ausgelesen werden. Dieser Mechanismus wird beispielsweise über einen DataAdapter ausgelöst, um separate Aktionen für das Aktualisieren von Daten zu definieren. Eine DataTable kann manuell erzeugt werden, wird aber auch durch die Fill-Methode des DataAdapters in einem DataSet automatisch erstellt und gefüllt. Sie dient der Bearbeitung, Navigation, Sortierung und Filterung von Daten im Speicher sowie der Erstellung von Sichten. DataTable programmatisch erzeugen Es ist sehr einfach, programmatisch eine DataTable anzulegen und in ein DataSet einzufügen. Beispiel dtKlimata Das folgende Beispiel ist ein Projekt vom Typ Konsolenanwendung, das ohne Maske und ohne Datenbankverbindung auskommt, da ein DataSet und eine DataTable programmatisch erstellt werden. Sie finden das Beispielprojekt auf der beiliegenden CD-ROM im Verzeichnis \Kapitel05\dtKlimata. In diesem Beispiel wird eine Datentabelle dtKlimata mit drei Datenspalten pkKlima, Klimaname und Temperatur angelegt und in ein DataSet eingefügt. Daten und Schema des DataSets werden anschließend als XML-Daten im Anwendungsverzeichnis ausgegeben. Listing 5.29 Programmatisch erzeugte DataTable im DataSet Dim dsDataSet As DataSet Dim dtKlimata As DataTable Sub Main() ' DataSet erzeugen Dim dsDataSet As DataSet dsDataSet = New DataSet() ' DataTable erzeugen Dim dtKlimata As DataTable dtKlimata = New DataTable("tblKlimata") ' Tabellenspalte mit Primärschlüssel definieren Dim dcNeu As DataColumn dcNeu = New DataColumn() 344 ADO.NET und Datenbanken dcNeu.ColumnName = "pkKlima" dcNeu.DataType = Type.GetType("System.Int32") ' Primärschlüsselwerte automatisch erzeugen dcNeu.AutoIncrement = True dcNeu.AutoIncrementSeed = 1 dcNeu.AutoIncrementStep = 1 dcNeu.AllowDBNull = False ' Tabellenspalte in die Tabelle einfügen dtKlimata.Columns.Add(dcNeu) ' Tabellenspalte einem Primärschlüssel-Array ' hinzufügen Dim arrPrimaryKey(0) As DataColumn arrPrimaryKey(0) = dcNeu ' Primärschlüssel in der Tabelle setzen dtKlimata.PrimaryKey = arrPrimaryKey ' Tabellenspalte für den Namen definieren dcNeu = New DataColumn() dcNeu.ColumnName = "Klimaname" dcNeu.DataType = Type.GetType("System.String") ' Tabellenspalte in die Tabelle einfügen dtKlimata.Columns.Add(dcNeu) ' Tabellenspalte für die Beschreibung definieren dcNeu = New DataColumn() dcNeu.ColumnName = "Temperatur" dcNeu.DataType = Type.GetType("System.Int32") ' Tabellenspalte in die Tabelle einfügen dtKlimata.Columns.Add(dcNeu) 'Tabelle dem DataSet hinzufügen dsDataSet.Tables.Add(dtKlimata) ' DataSet und Schema als XML speichern dsDataSet.WriteXmlSchema("dtKlimataSchema.xml") dsDataSet.WriteXml("dtKlimata.xml") End Sub Zunächst wird eine neue Datentabelle tblKlimata erzeugt, der eine Spalte pkKlima hinzugefügt wird, die Primärschlüssel werden soll. Jede Spalte, die den Primärschlüssel aufnehmen soll, muss gleichzeitig als Array von Datenspalten angelegt werden, das die Datentabelle über die Eigenschaft PrimaryKey aufnimmt. Außer der Schlüsselspalte werden noch die Datenspalten Klimaname und Temperatur vom Typ Zeichenkette und Integer hinzugefügt. Datenobjekte des DataSets PrimärschlüsselArray 345 Um die neue DataTable auszuwerten, wird sie in ein neues DataSet eingefügt, dessen Schema und Inhalte anschließend im XML-Format ausgegeben werden. Testen Sie das Beispiel und öffnen Sie danach die Datei dtKlimataSchema.xml im Projektausgabeverzeichnis \bin. Abbildung 5.30 Schema der DataTable dtKlimata im DataSet Das Schema enthält die Spaltendefinitionen und den Primärschlüssel der programmatisch erzeugten Datentabelle dtKlimata. DataTable-Eigenschaften Das Objekt DataTable hat folgende Eigenschaften: Name Beschreibung CaseSensitive Setzt oder liest einen Booleschen Wert, der festlegt, ob ein Zeichenkettenvergleich schreibungssensitiv ausgeführt wird. Wenn die Tabelle Teil eines DataSets ist, übernimmt sie diese Eigenschaft vom DataSet. Bei der programmatischen Erzeugung einer DataTable wird dieser Wert mit False vorgegeben. Tabelle 5.16 DataTable-Eigenschaften 346 ADO.NET und Datenbanken Name Beschreibung ChildRelations Gibt alle Detaildatenbeziehungen der Tabelle als Auflistung vom Typ DataRelationCollection zurück oder Nothing, wenn keine Detaildatenbeziehungen vorhanden sind Columns Gibt eine Auflistung der Spalten der Tabelle als Auflistung vom Typ DataColumnCollection zurück oder Nothing, wenn keine Spalten vorhanden sind Constraints Gibt eine Auflistung der Einschränkungen der Tabelle als Auflistung vom Typ ConstraintCollection zurück oder Nothing, wenn keine Einschränkungen vorhanden sind DataSet Gibt das DataSet, zu dem die Tabelle gehört, als Objekt zurück DefaultView Gibt ein DataView-Objekt zurück, das eine benutzerdefinierte, sortierte und gefilterte Sicht auf die Tabelle oder einen einzelnen Datensatz sein kann DisplayExpression Setzt oder liest eine Zeichenkette, die benutzt werden kann, die Tabelle in der Benutzeroberfläche zu kennzeichnen ExtendedProper- Gibt eine Auflistung der benutzerdefinierten Informationen als PropertyCollection zurück. Die Eigenschaft besitzt ties eine Methode Add, mit der ExtendedProperties als Zeichenketten hinzugefügt werden können: dtDataTable.ExtendedProperties.Add("Eingabe","Testdaten"). HasErrors Gibt einen Booleschen Wert zurück, der angibt, ob in einer der Datenzeilen der DataTable nach einer Aktion Fehler aufgetreten sind Locale Setzt oder liest die Lokalisierungsinformation, die für Zeichenkettenvergleiche in der DataTable ausgewertet wird MinimumCapacity Setzt oder liest die Größe, die DataTable zu Beginn hatte Namespace Setzt oder liest den Namensraum der XML-Repräsentation der Daten in dieser Tabelle ParentRelations Gibt die Auflistung von übergeordneten Beziehungen für diese Tabelle zurück oder Nothing, wenn keine existiert Prefix Setzt oder liest das Namensraum-Präfix der XML-Repräsentation der Daten in der DataTable PrimaryKey Setzt oder liest ein Array von DataColumn-Objekten, das die Primärschlüssel für die DataTable darstellt Tabelle 5.16 DataTable-Eigenschaften (Forts.) Datenobjekte des DataSets 347 Name Beschreibung Rows Gibt die Auflistung von DataRows zurück, die zu der DataTable gehören, oder Nothing, wenn keine DataRow vorhanden ist TableName Setzt oder liest den Namen der Tabelle. Der Name wird auch über die DataTableCollection des DataSets ausgewertet. Tabelle 5.16 DataTable-Eigenschaften (Forts.) DataTable-Methoden Eine DataTable stellt die folgenden Methoden zur Verfügung: Name Beschreibung AcceptChanges() Übernimmt alle Änderungen, die an der DataTable seit dem letzten Aufruf dieser Methode oder seit dem Laden der DataTable vorgenommen wurden. Ändert den Zustand aller geänderten Zeilen auf unchanged und ist daher erst nach dem Aufruf der Update-Methode sinnvoll BeginInit() Mit BeginInit kann gesteuert werden, wann eine DataTable initialisiert wird, die auf einer Maske oder in einer Komponente verwendet werden soll. Nach Aufruf von BeginInit wird zur Laufzeit auf den Aufruf von EndInit gewartet, um sicherzustellen, dass die DataTable nicht verwendet wird, bevor sie vollständig initialisiert wurde. BeginLoadData() Wird im Zusammenhang mit EndLoadData verwendet. Diese Methode deaktiviert Benachrichtigungen, IndexAktualisierungen und Einschränkungen, während Daten geladen werden. Clear() Löscht alle Daten aus der DataTable Clone() Kopiert die Struktur der DataTable inklusive des Schemas der DataTable und ihrer Einschränkungen Compute() Erwartet zwei Argumente als Zeichenketten. Wendet eine der beiden Aggregatfunktionen SUM oder COUNT, die als erstes Argument angegeben ist, auf die mit der im zweiten Argument angegebenen Bedingung gefilterten aktuellen Datenzeilen an. Copy() Kopiert die Struktur und Daten der DataTable Tabelle 5.17 DataTable-Methoden 348 ADO.NET und Datenbanken Name Beschreibung EndInit() Beendet die Initialisierung der DataTable in Verbindung mit BeginInit() zur Laufzeit und gibt sie zur Verwendung frei EndLoadData() Wird im Zusammenhang mit BeginLoadData verwendet. Diese Methode aktiviert Benachrichtigungen, Index-Aktualisierungen und Einschränkungen, nachdem Daten geladen wurden. GetChanges() Gibt eine Kopie der DataTable zurück, die alle Datenzeilen enthält, die seit dem Laden der DataTable oder dem letzten Aufruf von AcceptChanges geändert worden sind. GetErrors() Gibt ein Array von DataRows zurück, in denen ein Fehler aufgetreten ist ImportRow() Kopiert eine DataRow in eine DataTable, wobei alle Eigenschaften sowie der Originalinhalt und der aktuelle, möglicherweise geänderte Wert erhalten bleiben LoadDataRow() Sucht und aktualisiert eine bestimmte Datenzeile. Wenn diese nicht gefunden werden kann, wird sie mit den angegebenen Werten erstellt. NewRow() Erzeugt eine neue DataRow entsprechend dem Tabellenschema RejectChanges() Macht alle Änderungen, die seit dem Laden der DataTable oder dem letzten Aufruf von AcceptChanges gemacht wurden, rückgängig Reset() Versetzt die DataTable in ihren Originalzustand zurück Select() Gibt ein Array von DataRows zurück, das nach dem Primärschlüssel, sofern vorhanden, sortiert ist Tabelle 5.17 DataTable-Methoden (Forts.) Ereignisse der DataTable Datentabellen stellen Ereignisse zur Verfügung, mit denen Veränderungen an Daten in Spalten und Zeilen gehandhabt werden können. Die DataTable kann sechs Ereignisse auslösen: Datenobjekte des DataSets 349 Ereignis Beschreibung ColumnChanged Tritt ein, wenn ein Wert in eine Spalte erfolgreich eingefügt werden konnte ColumnChanging Tritt ein, wenn ein Wert in eine Spalte eingetragen wird RowChanged Tritt ein, nachdem eine Datenzeile erfolgreich aktualisiert wurde RowChanging Tritt ein, wenn eine Datenzeile in der Tabelle verändert wird RowDeleted Tritt ein, wenn eine Datenzeile in der Tabelle als gelöscht markiert wurde RowDeleting Tritt ein, bevor eine Datenzeile in der Tabelle als gelöscht markiert wird Tabelle 5.18 DataTable-Ereignisse Die Ereignisse der DataTable sind paarweise angelegt, beispielsweise tritt RowChanging während der Änderung einer Datenzeile auf, RowChanged, wenn sie erfolgreich geändert wurde. GetErrors nach RowChanging auswerten Für eine Anwendung mit datengebundenen Eingabefeldern stellen DataTable-Ereignisse eine gute Möglichkeit dar, eingegebene Daten zu überprüfen. Problematische Datensätze können über die GetErrors-Methode der DataTable entdeckt werden, nachdem das RowChanging-Ereignis ausgelöst worden ist. In der Fortsetzung des vorausgegangenen Beispiels sollen die Datenzeilen der DataTable programmatisch geändert werden. In einer Prozedur DataTable_Füllen werden zunächst drei Tabellenzeilen erzeugt, die mit Hilfe der Prozedur DataTable_Ändern geändert werden, um das Änderungsereignis auszulösen. Listing 5.30 Prozedur zum Füllen der Tabelle Private Sub DataTable_Füllen() Dim i As Integer Dim drInhalt As DataRow For i = 1 To 3 drInhalt = dtKlimata.NewRow drInhalt("Klimaname") = "Name_" + i.ToString drInhalt("Temperatur") = Cint(Rnd(i) * 30) dtKlimata.Rows.Add(drInhalt) 350 ADO.NET und Datenbanken Next End Sub Listing 5.31 Prozedur zum Ändern einer Datenzeile Private Sub DataTable_Ändern() dtKlimata.Rows.Item(1)("Beschreibung") = 20 End Sub Ereignisprozeduren Um ein Ereignis auszuwerten, muss zuerst eine entsprechende Ereignisprozedur angelegt werden. Listing 5.32 Ereignisprozedur für die Spaltenänderung Private Sub SpaltenÄnderung( _ ByVal sender As Object, _ ByVal e As DataColumnChangeEventArgs) MsgBox("In Datenzeile: " & _ e.Row("Klimaname").ToString & _ " soll die Spalte " & e.Column.ColumnName & _ " in: " + CStr(e.ProposedValue) & _ " geändert werden.") End Sub Zum Aufruf der Ereignistests wird die Prozedur Main ergänzt und darin das Füllen der Datenzeilen nach dem Einsetzen der Tabelle in das DataSet aufgerufen. Danach wird die Ereignisprozedur der Zeilenänderung an den Ereignishandler der Tabelle gebunden und anschließend die Änderung ausgelöst. Listing 5.33 Auslösen des Änderungsereignisses, Auszug aus erweiterter Prozedur Sub Main() ' ... dsDataSet.Tables.Add(dtKlimata) ' Tabelle mit einigen Zeilen füllen DataTable_Füllen() ' Ereignisbehandlung an DataTable binden AddHandler dtKlimata.ColumnChanging, _ New DataColumnChangeEventHandler( _ AddressOf SpaltenÄnderung) Datenobjekte des DataSets 351 ' Tabelle ändern DataTable_Ändern() ' Fortsetzung Sub Main mit XML-Ausgabe dsDataSet.WriteXmlSchema("dtKlimataSchema.xml") dsDataSet.WriteXml("dtKlimata.xml") ' ... Nach dem Start der Anwendung erscheint eine Meldung, die durch den Ereignishandler der Datentabelle ausgelöst wird. Abbildung 5.31 Änderungsmeldung Abbildung 5.32 Geänderte DataTable dtKlimata im DataSet Die Datei dtKlimata.xml im Projektausgabeverzeichnis \bin enthält die geänderten Daten der Datentabelle dtKlimata. 352 ADO.NET und Datenbanken Eigenschaften von Änderungsereignissen Änderungsereignisse der DataTable haben die folgenden Eigenschaften: Eigenschaft Beschreibung Column Gibt die Datenspalte zurück, deren Wert geändert wird ProposedValue Setzt oder liest den neuen Wert, der in die Spalte eingetragen werden soll Row Gibt die Datenzeile zurück, in der geändert wird Tabelle 5.19 Eigenschaften des Änderungsereignisses einer DataTable 5.7.2 DataRow Eine DataRow stellt eine einzelne Datenzeile in einer DataTable dar. Werden Daten eingefügt oder geändert, die dem Tabellenschema widersprechen, wird in der Eigenschaft RowError eine Fehlermeldung gesetzt, die vor einer Aktualisierung der ursprünglichen Datenquelle ausgewertet werden kann. Eine DataRow wird über die NewRow-Methode einer Datentabelle erzeugt. dtDataTable. NewRow Listing 5.34 Erzeugung einer DataRow Dim dtDataTable as DataTable Dim drNeu as DataRow drNeu = dtDataTable.NewRow() drNeu("pkTabelle") = 1 drNeu("Testname") = "Neu" drNeu("Beschreibung" ) = "Testzeile" dtDatatable.Rows.Add(drNeu) Eine neue Datenzeile wird immer mit dem Schema und den Spalteninformationen einer bestehenden DataTable angelegt. Sie kann auch zuerst mit Daten gefüllt werden, um anschließend zur Auflistung der Datenzeilen der DataTable hinzugefügt zu werden. Datenobjekte des DataSets 353 DataRow-Eigenschaften DataRow hat die folgenden Eigenschaften: Name Beschreibung HasErrors Gibt einen Booleschen Wert zurück, der angibt, ob in der Datenzeile Fehler aufgetreten sind Item Setzt oder liest Daten in einer angegebenen Spalte ItemArray Gibt alle Werte dieser Zeile in einem Array zurück bzw. setzt alle Werte dieser Zeile aus einem Array RowError Setzt oder liest eine Fehlerbeschreibung für eine Zeile RowState Gibt den aktuellen Zustand der Datenzeile zurück und wird zur Auswertung von Datenänderungen genutzt Table Gibt die DataTable zurück, zu der diese DataRow gehört Tabelle 5.20 DataRow-Eigenschaften DataRow-Methoden DataRow stellt die folgenden Methoden zur Verfügung: Name Beschreibung AcceptChanges() Übernimmt alle Änderungen, die an der DataRow seit dem letzten Aufruf dieser Methode oder seit dem Laden der DataRow vorgenommen wurden BeginEdit() Startet eine Änderungsaktion an der Datenzeile, während der alle Ereignisse deaktiviert sind. Diese Methode wird zusammen mit EndEdit() und CancelEdit() verwendet. CancelEdit() Bricht die mit BeginEdit() eingeleitete Änderung der Datenzeile ab ClearErrors() Mit dieser Methode werden alle Fehler gelöscht, die aus Zeilenfehlern oder aus der Methode SetColumnError() stammen Delete() Löscht die Datenzeile EndEdit() Beendet die Änderungen an der Datenzeile Tabelle 5.21 DataRow-Methoden 354 ADO.NET und Datenbanken Name Beschreibung GetChildRows() Gibt die Detaildaten einer Datenzeile zurück. Diese Methode wird mit einer DataRelation und der DataRowVersion aufgerufen. GetColumnError() Gibt die Fehlerbeschreibung einer Datenspalte zurück. Wird mit einem DataColumn-Objekt, einer Ordinalzahl oder dem Spaltennamen als Argument aufgerufen GetColumnsInError() Gibt ein Array der Spalten zurück, in denen Fehler aufgetreten sind GetParentRow() Gibt die der aktuellen Datenzeile übergeordnete Datenzeile zurück. Diese Methode wird mit einer DataRelation und der DataRowVersion aufgerufen. GetParentRows() Gibt ein Array der übergeordneten Datenzeilen zurück. Diese Methode wird mit einer DataRelation und der DataRowVersion aufgerufen. HasVersion() Gibt einen Booleschen Wert zurück, der angibt, ob eine bestimmte Version der Datenzeile existiert. Wird mit einem der DataRowVersion-Werte Current, Default, Original oder Proposed als Argument aufgerufen. IsNull() Gibt einen Booleschen Wert zurück, der angibt, ob die als Argument angegebene Spalte einen NULL-Wert enthält RejectChanges() Verwirft alle Änderungen, die an der Datenzeile gemacht wurden, seit das letzte Mal AcceptChanges aufgerufen wurde oder die Datenzeile geladen wurde SetColumnError() Setzt die Fehlerbeschreibung für die als Argument angegebene Datenspalte SetParentRow() Setzt die der aktuellen Datenzeile übergeordnete Datenzeile. Diese Methode wird mit einer Datenzeile oder einer DataRelation und einer Datenzeile aufgerufen. Tabelle 5.21 DataRow-Methoden (Forts.) Die Verwendung der GetChildRows- und GetParentRows-Methoden wird in Abschnitt 5.8.1, »DataRelation«, beschrieben. Werte der DataRowVersion Die mit der HasVersion-Methode der DataRow ausgewertete Eigenschaft DataRowVersion kann folgende Werte annehmen: Datenobjekte des DataSets 355 Eigenschaft Beschreibung Current Aktueller Wert der Datenzeile Default Zustand der Datenzeile, wenn für neu erstellte Datenzeilen Vorgabewerte definiert sind und angewendet wurden Original Als unverändert behandelte Version der Datenzeile, die nach der Erstellung oder dem Aufruf von AcceptChanges vorliegt Proposed Wert nach der Änderung von Werten, bevor AcceptChanges aufgerufen wurde Tabelle 5.22 Mögliche Werte der DataRowVersion 5.7.3 DataView Eine DataView ist eine optional gefilterte Sicht auf die Daten in einem DataSet. Es können mehrere DataViews für ein DataSet erstellt werden, um damit Daten zu filtern, zu sortieren oder zu suchen. Steuerelemente können direkt an DataViews gebunden werden. Für eine DataView können Eigenschaften festgelegt werden, die von jenen der Datentabelle abweichen, d.h., Sie können Daten filtern, sortieren und vor dem Editieren oder Löschen schützen, ohne die zugrunde liegende DataTable zu verändern. Beispiel dvDropDown In einem einfachen Beispiel soll ein Dropdown mit den Gewürznamen aus einer DataView gefüllt werden. Der Inhalt des Dropdowns soll sortiert und gefiltert werden. Sie finden das fertige Projekt auf der beiliegenden CD-ROM unter \Kapitel05\dvDropDown. Legen Sie eine neue Windows-Anwendung an, blenden Sie in der Entwurfsansicht von Form1 die Toolbox ein und platzieren Sie aus dem Abschnitt Windows Forms eine Combobox auf der Maske, die Sie im Eigenschaftenfenster DropDown benennen. Auf der Maske sollen außer dem Gewürznamen im Dropdown auch die anderen Daten des ausgewählten Gewürzes angezeigt werden. Ziehen Sie dazu ein Label auf die Form1, das Sie lblSelectedID nennen, und fügen Sie drei Textboxen mit den Namen txtName, txtGattung und txtBeschreibung hinzu. 356 ADO.NET und Datenbanken Fügen Sie in der Code-Ansicht von Form1 Verweise auf die Namensräume ConfigurationSettings, OleDB und SqlClient hinzu: Imports System.Configuration.ConfigurationSettings Imports System.Data.OleDb Imports System.Data.SqlClient Deklarieren Sie ein DataSet, eine DataTable und eine DataView. Dim dsDropDown As DataSet Dim dtDropDown As DataTable Dim dvDropDown As DataView In der Prozedur DropDown_Füllen soll ein DataSet über einen Datenadapter mit einer einzigen Tabelle gefüllt werden. Der Beispielcode verwendet Datenprovider-unabhängigen Code, das Beispiel funktioniert daher sowohl mit einer Access-Datenbank im Anwendungsverzeichnis \bin als auch mit der Datenbank dbWürzen auf dem SQL Server. Die Information über den zu wählenden Datenprovider wird zur Laufzeit der Konfigurationsdatei <Anwendungsname>. exe.config entnommen. Listing 5.35 Code zum Füllen des Dropdowns aus einer DataView Private Sub DropDown_Füllen() Dim _str As String _str = "SELECT pkGewürz,Gewürzname, " & _ " Gattung,Beschreibung " & _ " FROM tblGewürze" ' Allgemeiner Datenbankzugriff Dim _conn As IDbConnection Dim _cmd As IDbCommand Dim _da As IDbDataAdapter ' Verbindungsauswahl auswerten Select Case AppSettings("Verbindungstyp").ToUpper Case "SQL" _conn = New SqlConnection( _ AppSettings("conSQL")) _cmd = New SqlCommand(_str) _cmd.Connection = _conn _da = New SqlDataAdapter(_cmd) Case "OLEDB" _conn = New OleDbConnection( _ AppSettings("conOleDB")) Datenobjekte des DataSets 357 _cmd = New OleDbCommand(_str) _cmd.Connection = _conn _da = New OleDbDataAdapter(_cmd) End Select ' DataSet über den DataAdapter füllen lassen dsDropDown = New DataSet() _da.Fill(dsDropDown) ' DataView für DataSet erstellen dtDropDown = dsDropDown.Tables(0) dvDropDown = dsDropDown.DefaultViewManager. _ CreateDataView(dtDropDown) ' DropDown an DataView binden DropDown.DataSource = dvDropDown DropDown.ValueMember = "pkGewürz" DropDown.DisplayMember = "Gewürzname" End Sub Die DataView wird mit Hilfe des DefaultViewManagers des DataSets erstellt, dessen CreateDataView-Methode auf die einzige Tabelle angewendet wird. Datenbindung Das Dropdown wird über seine Eigenschaften DataSource, DataMember und ValueMember an die DataView gebunden. Die Textfeldinhalte werden direkt an Tabellenspalten gebunden. Listing 5.36 Datenbindung der Formularfelder Private Sub Datenbindung_Formularfelder() ' Steuerelemente an DataView binden Try lblSelectedID.DataBindings.Add("Text", _ dvDropDown, "pkGewürz") txtBeschreibung.DataBindings.Add("Text", _ dvDropDown, "Beschreibung") txtGattung.DataBindings.Add("Text", _ dvDropDown, "Gattung") txtGewürzname.DataBindings.Add("Text", _ dvDropDown, "Gewürzname") Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub 358 ADO.NET und Datenbanken Der Code zum Füllen des Dropdowns sowie die Datenbindungen werden beim Laden der Maske aus der Ereignisprozedur Form1_Load aufgerufen. Listing 5.37 Aktionen beim Laden der Maske Private Sub Form1_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load DropDown.Items.Clear() DropDown_Füllen() Datenbindung_Formularfelder() End Sub Wenn Sie das Projekt ausführen, sehen Sie als Ergebnis eine Datenmaske mit gefülltem Dropdown (siehe Abbildung 5.33). Abbildung 5.33 Datenmaske mit Dropdown aus DataView Filtern und Sortieren Um die Sortierungs- und Filtermöglichkeiten der DataView zu testen, fügen Sie der Maske eine Schaltfläche mit dem Namen btnFiltern und eine Textbox mit dem Namen txtFilter hinzu. Daten filtern und sortieren mit DataView Klicken Sie doppelt auf btnFiltern, um den Code für die Filterbedingung anzugeben. Datenobjekte des DataSets 359 Listing 5.38 Setzen der Filterbedingungen für die DataView Private Sub btnFiltern_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnFiltern.Click Try ' Filterbedingungen setzen dvDropDown.Sort = "Gewürzname DESC" dvDropDown.RowFilter = txtFilter.Text Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub Bevor RowFilter verwendet werden kann, muss die Eigenschaft Sort eine Sortierbedingung erhalten. Die Eigenschaft RowFilter entnimmt die Filterbedingung der Textbox txtFilter. Starten Sie die Anwendung und geben Sie in die neue Textbox als Filterbedingung "Gewürzname like 'K%' and id < 1023" ein. Öffnen Sie das Dropdown, um die gefilterten Datensätze zu sehen. Where und Order by Beide genannten Eigenschaften akzeptieren Fragmente von SQLAnweisungen, dabei funktioniert RowFilter ähnlich wie ein WHEREFilter in SQL, Sort ähnlich wie eine ORDER BY-Klausel. Abbildung 5.34 Sortierte und gefilterte Daten im Dropdown 360 ADO.NET und Datenbanken Daten suchen Fügen Sie der Maske eine Schaltfläche mit dem Namen btnSuchen und eine Textbox mit dem Namen txtSuche hinzu. Um einzelne Datensätze gezielt zu suchen, wird zur Sicherheit zuerst der Filter deaktiviert. Die Suche besteht im Auswerten der Methode Find, die entweder die Zeilenposition der gesuchten Zeile oder, wenn nichts gefunden wurde, -1 als Wert zurückgibt. Methode Find() Listing 5.39 Suchfunktion Private Sub btnSuchen_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnSuchen.Click ' Filter für Suche zurücksetzen txtFilter.Text = "" dvDropDown.RowFilter = "" dvDropDown.Sort = "Gewürzname ASC" ' Datensatznummer suchen Dim _int As Integer _int = dvDropDown.Find(txtSuche.Text) ' Zum gefundenen Datensatz navigieren Me.BindingContext.Item(dvDropDown).Position = _int End Sub Abbildung 5.35 Suche in der Datenmaske Datenobjekte des DataSets 361 Mit der Position der gefundenen Zeile kann über die Datenbindung der Maske der Datensatz in allen gebundenen Steuerelementen angezeigt werden. Gibt es als Ergebnis einer Suche mehrere Datenzeilen, speichert die Methode FindRows alle Fundstellen als Array. DataView-Eigenschaften Die Eigenschaften einer DataView sind: Name Eigenschaften AllowDelete Setzt oder liest den Wert, der festlegt, ob das Löschen von Daten zugelassen ist AllowEdit Setzt oder liest den Wert, der festlegt, ob das Ändern von Daten zugelassen ist AllowNew Setzt oder liest den Wert, der festlegt, ob Daten über die Methode AddNew hinzugefügt werden dürfen ApplyDefaultSort Setzt oder liest den Wert, der festlegt, ob nach der Standardvorgabe sortiert werden soll Count Liest die Anzahl der Datensätze aus, nachdem RowFilter und RowStateFilter angewendet worden sind DataViewManager Gibt den DataViewManager zurück, der mit dieser DataView verbunden ist Item Identifiziert eine Datenzeile in der Datentabelle in der Form dvDataView.Item(4) RowFilter Setzt oder liest die Zeichenkette, die eine Anweisung enthält, mit der die Datenzeilen für die Sichtbarkeit in der DataView gefiltert werden sollen Sort Setzt oder liest die Spalten, nach denen sortiert werden soll, und legt die Sortierrichtung fest. Enthält die Spaltennamen und die Sortierrichtung ähnlich der SQLKlausel ORDER BY Table Setzt oder liest die DataTable, aus der die DataView mit Daten versorgt wird. Diese Eigenschaft kann nur von Nothing auf einen Wert gesetzt werden. Tabelle 5.23 DataView-Eigenschaften 362 ADO.NET und Datenbanken DataView-Methoden Eine DataView stellt folgende Methoden zur Verfügung: Name Beschreibung AddNew() Fügt der DataView eine neue Datenzeile vom Typ DataRowView hinzu BeginInit() Mit BeginInit kann gesteuert werden, wann eine DataView initialisiert wird, die auf einer Maske oder in einer Komponente verwendet werden soll. Nach Aufruf von BeginInit wird zur Laufzeit auf den Aufruf von EndInit gewartet, um sicherzustellen, dass die DataView nicht verwendet wird, bevor sie vollständig initialisiert wurde. Delete() Löscht eine Datenzeile an der angegebenen Zeilenposition in der Form dvDataView.Delete(4) EndInit() Gibt das Ende der Initialisierung der DataView in Verbindung mit BeginInit() zur Laufzeit an und gibt sie zur Verwendung frei Find() Gibt die Zeilenposition einer zu suchenden Datenzeile zurück, die über einen oder mehrere in Sort definierte Schlüsselwerte identifiziert wird FindRows() Gibt die Zeilenposition mehrerer zu suchender Datenzeilen als Array zurück, die über einen oder mehrere in Sort definierte Schlüsselwerte identifiziert werden Tabelle 5.24 DataView-Methoden 5.8 Tabellenverknüpfungen Das Laden mehrerer Tabellen in ein DataSet überträgt keine in der Datenbank definierten Tabellenverknüpfungen. Unter Umständen fehlen deshalb im DataSet notwendige Beziehungen, mit denen in der Datenbank die Datenintegrität überwacht wird. Sie können diese Beziehungen im DataSet über DataRelation-Objekte entsprechend nachbilden oder auch bewusst abweichend definieren. 5.8.1 Verknüpfte Tabellen DataRelation DataRelation-Objekte sind Teil des DataSets und werden zur Herstellung von Tabellenbeziehungen durch die Verknüpfung von überund untergeordneten Datenspalten desselben Datentyps verwendet. Diese Beziehungen legen Regeln und Abhängigkeiten für die Datenin- Tabellenverknüpfungen 363 tegrität in den beteiligten Datentabellen fest und werden bei allen Aktionen im DataSet überwacht. Mit Hilfe von DataRelation-Objekten ist es auch möglich, hierarchische Datenmengen programmatisch zu erzeugen. DataRelation anlegen Eine DataRelation wird zwischen einer oder mehreren Spalten in einer Hauptdatentabelle und einer gleichen Anzahl von Spalten in einer Detaildatentabelle erzeugt. Beispiel rlDataGrid Im folgenden Beispiel wird eine Beziehung zwischen zwei Datentabellen im Programmcode erzeugt. Der Vorgang entspricht inhaltlich dem in Abbildung 5.24 dargestellten Einrichten einer Tabellenbeziehung aus der Schemadarstellung eines DataSets. Sie finden das fertige Beispiel rlDataGrid auf der beiliegenden CD-ROM im Verzeichnis \Kapitel05\rlDataGrid. Legen Sie eine neue Windows-Anwendung an und ziehen Sie in der Entwurfsansicht ein DataGrid aus der Toolbox auf die Maske Form1. Fügen Sie im Projektmappen-Explorer die Datei App.config hinzu, in der Sie wie gehabt die Verbindungszeichenketten verschiedener Datenbanken speichern können. Der im Folgenden gezeigte Beispielcode funktioniert gleichermaßen mit der SQL Server- und der AccessVariante der Datenbank dbWürzen. Fügen Sie in der Code-Ansicht von Form1 Verweise auf die Namensräume ConfigurationSettings, OleDb und SqlClient hinzu: Imports System.Configuration.ConfigurationSettings Imports System.Data.OleDb Imports System.Data.SqlClient Deklarieren Sie ein DataSet ds, das die zu verknüpfenden Tabellen aufnehmen soll: Private ds As New DataSet() Das Laden der Daten findet in der Prozedur TabellenLaden statt, die aus der Ereignisprozedur Form1_Load aufgerufen wird. 364 ADO.NET und Datenbanken Listing 5.40 Laden der Tabellen Private Sub TabellenLaden() ' SQL Anweisungen Dim _str1 As String = "SELECT * " & _ " FROM tblKlimata " Dim _str2 As String = "SELECT * " & _ " FROM tblRegionen" ' Allgemeiner Datenbankzugriff Dim _conn As IDbConnection Dim _cmd1 As IDbCommand Dim _cmd2 As IDbCommand ' Verbindungsauswahl auswerten Select Case AppSettings("Verbindungstyp").ToUpper Case "SQL" _conn = New SqlConnection( _ AppSettings("conSQL")) _cmd1 = New SqlCommand(_str1) _cmd1.Connection = _conn _cmd2 = New SqlCommand(_str2) _cmd2.Connection = _conn ' DataSet über DataAdapter füllen Dim _da1 As SqlDataAdapter _da1 = New SqlDataAdapter(_cmd1) _da1.Fill(ds, "Klimata") Dim _da2 As SqlDataAdapter _da2 = New SqlDataAdapter(_cmd2) _da2.Fill(ds, "Regionen") Case "OLEDB" _conn = New OleDbConnection( _ AppSettings("conOleDB")) _cmd1 = New OleDbCommand(_str1) _cmd1.Connection = _conn _cmd2 = New OleDbCommand(_str2) _cmd2.Connection = _conn ' DataSet über DataAdapter füllen Dim _da1 As OleDbDataAdapter _da1 = New OleDbDataAdapter(_cmd1) _da1.Fill(ds, "Klimata") Dim _da2 As OleDbDataAdapter _da2 = New OleDbDataAdapter(_cmd2) Tabellenverknüpfungen 365 _da2.Fill(ds, "Regionen") End Select End Sub Die Daten werden in die Tabellen Klimata und Regionen des DataSets geladen, für die in der Prozedur TabellenVerknüpfen eine Beziehung definiert wird. Listing 5.41 Erzeugen einer DataRelation Private Sub TabellenVerknüpfen() ' Tabellenbeziehung hinzufügen ds.Relations.Add( _ New DataRelation( _ "relKlimataRegionen", _ ds.Tables("Klimata").Columns("pkKlima"), _ ds.Tables("Regionen").Columns("fkKlima"))) End Sub Ähnlich wie bei einer JOIN-Anweisung in SQL wird die Zuordnung über die Tabellennamen und die zu verknüpfenden Spalten angegeben. Im Gegensatz zur JOIN-Anweisung ist eine DataRelation auch außerhalb von SQL-Anweisungen wirksam. Die Syntax lautet: New DataRelation(RelationName, ParentColumn, ChildColumn) Sollen mehrere Spalten gleichzeitig verknüpft werden, kann anstelle einer einzelnen DataColumn auch ein Array von Datenspalten angegeben werden. Die neu erstellte Tabellenbeziehung wird erst mit der Einfügung in die Auflistung der DataRelations des DataSets wirksam. DataRelation-Eigenschaften Ein DataRelation-Objekt hat folgende Eigenschaften: Name Beschreibung ChildColumns Ruft die untergeordneten DataColumn-Objekte dieser Beziehung ab ChildKeyConstraint Ruft die ForeignKeyConstraint für die Beziehung ab Tabelle 5.25 DataRelation-Eigenschaften 366 ADO.NET und Datenbanken Name Beschreibung ChildTable Ruft die untergeordnete Tabelle dieser Beziehung ab DataSet Ruft das DataSet ab, zudem die DataRelation gehört ExtendedProperties Ruft die Auflistung ab, in der angepasste Eigenschaften gespeichert werden Nested Ruft einen Wert ab, der angibt, ob DataRelationObjekte geschachtelt sind, oder legt diesen fest ParentColumns Ruft ein Array von DataColumn-Objekten ab, die die übergeordneten Spalten dieser DataRelation sind ParentKeyConstraint Ruft die UniqueConstraint ab, durch die sichergestellt wird, dass Werte in der übergeordneten Spalte einer DataRelation eindeutig sind ParentTable Ruft die übergeordnete DataTable dieser DataRela- tion ab RelationName Ruft den Namen ab, der zum Abrufen einer DataRelation aus der DataRelationCollection verwendet wird, oder legt diesen fest Tabelle 5.25 DataRelation-Eigenschaften (Forts.) 5.8.2 Darstellung verknüpfter Tabellen Zur Anzeige der verknüpften Tabellen in einem DataGrid genügt es, dessen DataSource-Eigenschaft an die Tabelle mit den Hauptdaten zu binden. Listing 5.42 Anzeigen der verknüpften Daten Private Sub TabellenAnzeigen() ' Verknüpfte Tabellen in einem DataGrid anzeigen DataGrid1.DataSource = ds.Tables("Klimata") End Sub Ergänzen Sie in Form1_Load den Aufruf der Prozeduren TabellenVerknüpfen und TabellenAnzeige und starten Sie dann die Anwendung rlDataGrid. Listing 5.43 Aufruf der Verknüpfung und Anzeige Private Sub Form1_Load( _ ByVal sender As Object, _ Tabellenverknüpfungen 367 ByVal e As System.EventArgs) _ Handles MyBase.Load Call TabellenLaden() Call TabellenVerknüpfen() Call TabellenAnzeigen() End Sub Abbildung 5.36 Anzeige verknüpfter Tabellen im DataGrid Im DataGrid wird zusätzlich zum Inhalt der Hauptdatentabelle Klimata unter jedem Knoten im Zeilenkopf ein Link eingeblendet, der den Namen der DataRelation relKlimataRegionen trägt. Wenn Sie diesem Link folgen, wird im DataGrid danach die Detaildatentabelle Regionen angezeigt (siehe Abbildung 5.37). Dieser Vorgang geschieht selbsttätig durch Interaktion des DataGrids mit den Methoden der DataRelation, d.h. ohne einen Wechsel der Datenbindung. Abbildung 5.37 Anzeige einer verknüpften Detaildatentabelle im DataGrid Navigation im DataGrid 368 In der Kopfzeile des DataGrids werden in der Detaildatenanzeige der Tabellenname und der Inhalt des Hauptdatensatzes angezeigt. Über die Symbole auf der rechten Seite kann zur Hauptdatentabelle zurück navigiert oder die Hauptdatensatzinformation ausgeblendet werden. Die Detaildatentabelle könnte im Prinzip Links auf weitere Untertabellen enthalten, die auf die gleiche Art zu erreichen wären. ADO.NET und Datenbanken In diesem Beispiel wird nur eine einfache Relation zwischen zwei Tabellen dargestellt. Ein komplexeres Anwendungsbeispiel von Relationen mehrerer hierarchisch verknüpfter Tabellen finden Sie im Abschnitt 5.11.1, »Datengebundenes Treeview«. 5.8.3 Zugriff auf Detaildaten Der Zugriff auf die Detaildaten in einer verknüpften Tabelle lässt sich auch programmatisch einrichten. Die Detaildaten einer DataRelation sind über die GetChildRows-Methode einer einzelnen DataRow in der Hauptdatentabelle zugänglich und werden dabei in ein Array von Datenzeilen übertragen. Der Aufruf folgt der Syntax: RowArray() = ds.Tables(TabellenName).Rows(Index). _ GetChildRows(RelationName) GetChildRows testen Erweitern Sie die Anwendung um eine Prozedur zum Test der GetChildRows-Methode, die beim Klick auf einen Datensatz in der Hauptdatentabelle ausgelöst wird. Wählen Sie dazu aus dem linken Codenavigations-Dropdown DataGrid1, dann aus dem rechten MouseDown, um die beim Klick auf das DataGrid1 auszulösende Ereignisprozedur anzulegen. Der Klick auf das DataGrid lässt sich über dessen HitTest-Methode auswerten, die die Koordinaten einer angeklickten Tabellenzelle zurückgibt. Für die DataRow in der dazugehörigen Tabellenzeile wird im Beispiel mit GetChildRows die Anzahl der vorhandenen Detaildatensätze ausgelesen und im Ausgabefenster angezeigt. Starten Sie die Anwendung im Debugmodus, um die Ausgabe der Testprozedur zu sehen. HitTest Listing 5.44 Zugriff auf die Detaildaten einer verknüpften Tabelle Private Sub DataGrid1_MouseDown( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles DataGrid1.MouseDown ' Koordinaten des Klicks auf das DataGrid auswerten Dim htHitTest As _ System.Windows.Forms.DataGrid.HitTestInfo Tabellenverknüpfungen 369 htHitTest = DataGrid1.HitTest(e.X, e.Y) If htHitTest.Row >= 0 Then ' Datenzeile im DataGrid ermitteln Dim _intRow As Integer _intRow = DataGrid1.Item(htHitTest.Row - 1, 0) ' Detaildaten der Relation in Array laden Dim arrRow() As DataRow arrRow = ds.Tables("Klimata").Rows(_intRow). _ GetChildRows("relKlimataRegionen") Console.WriteLine() Console.Write(" Es sind diesem Klima " & _ arrRow.Length & " Regionen zugeordnet") End If End Sub GetParentRows In gleicher Weise erhalten Sie Zugriff auf den Hauptdatensatz eines Detaildatensatzes, indem Sie die GetParentRows-Methode einer DataRow aus der Detaildatentabelle anwenden. In diesem Fall ist das Ergebnis kein Array, sondern ein einzelner Datensatz. 5.9 Datenauswahl und Datenaktualisierung Wie im Abschnitt 5.5, »Datenadapter für das DataSet«, dargestellt wurde, verwenden Datenadapter zum Aktualisieren, Löschen und Einfügen von Datensätzen in der Datenbank verschiedene CommandObjekte mit eigenen SQL-Anweisungen. Bei der Verwendung eines Assistenten zur Konfiguration eines Datenadapters, wie es in Abschnitt 5.5.2, »Datenadapter mit Assistenten konfigurieren«, beschrieben ist, werden für eine gegebene SQL-Anweisung zur Datenauswahl automatisch die entsprechenden SQL-Anweisungen oder Gespeicherten Prozeduren zur Aktualisierung der Datenbank erstellt. Automatische Aktualisierung 370 Das Objekt, das diese Aufgabe übernimmt, ist der CommandBuilder, der auch außerhalb des Datenadapter-Assistenten eingesetzt werden kann. Gegenüber manuell angelegten Command-Objekten zur Aktualisierung der Datenbank hat die Verwendung eines CommandBuilderObjektes den Vorteil, dass nach einer Änderung der SQL-Anweisung ADO.NET und Datenbanken für die Datenauswahl die SQL-Anweisungen zur Datenaktualisierung automatisch erneuert werden. 5.9.1 CommandBuilder Die Verwendung des CommandBuilders im Programmcode ist denkbar einfach: Für ein manuell erstelltes DataAdapter-Objekt, das mit einem gültigen Command-Objekt zum Füllen eines DataSets verwendet wird, genügt die bloße Erzeugung eines CommandBuilder-Objektes, um die Aktualisierung der Datenquelle zu ermöglichen. Listing 5.45 CommandBuilder für Datenadapter erzeugen ' Pseudocode zur Verdeutlichung des Verfahrens _cmd = New SqlCommand(SELECT-Anweisung) ' DataAdapter initialisieren _da = New SqlDataAdapter(_cmd) _da.Fill(ds) ' Datenmanipulation in der Anwendung ' ... Try ' INSERT-, DELETE- und UPDATE-Anweisungen ' vom CommandBuilder erstellen lassen Dim _cb As New SqlCommandBuilder(_da) _da.Update(ds) Catch ex As Exception ' ... End Try Ein CommandBuilder-Objekt wird für einen einzelnen DataAdapter angelegt und stellt die automatisch erzeugten SQL-Anweisungen über seine Methoden GetInsertCommand, GetDeleteCommand und GetUpdateCommand zur Verfügung. Diese Methoden können zum Auslesen der SQL-Anweisungen genutzt werden, um diese manuell zu verändern oder zu ergänzen. Listing 5.46 CommandBuilder-Anweisungen auslesen ' Pseudocode zur Verdeutlichung des Verfahrens _cmd = New SqlCommand(SELECT-Anweisung) ' DataAdapter initialisieren _da = New SqlDataAdapter(_cmd) _da.Fill(ds) Datenauswahl und Datenaktualisierung 371 ' Datenmanipulation in der Anwendung ' ... Try Dim _cb As New SqlCommandBuilder(_da) ' INSERT-, DELETE- und UPDATE-Anweisungen ' des CommandBuilders auslesen ' und ggf. nachbearbeiten _da.InsertCommand = _cb.GetInsertCommand _da.DeleteCommand = _cb.GetDeleteCommand _da.UpdateCommand = _cb.GetUpdateCommand _da.Update(ds) Catch ex As Exception ' ... End Try Der CommandBuilder versagt bei der Verwendung von unzulässigen Tabellen- und Spaltennamen, die vordefinierten Begriffen entsprechen (siehe Abschnitt 4.2.3, »Benennungsregeln«). Es ist möglich, mit Hilfe der Eigenschaften QuotePrefix und QuoteSuffix diese Namen ähnlich wie in Access in die Klammerzeichen [ ] einzubetten, um damit einige Probleme des CommandBuilders zu vermeiden. Die bessere Methode ist aber immer noch die konsequente Vermeidung vordefinierter Begriffe. CommandBuilder-Eigenschaften Ein CommandBuilder-Objekt hat folgende Eigenschaften: Name Beschreibung DataAdapter Ruft ein DataAdapter-Objekt ab, für das automatisch SQLAnweisungen oder Transact-SQL-Anweisungen generiert werden, oder legt dieses fest QuotePrefix Ruft das oder die Anfangszeichen ab, die beim Angeben von Namen für SQL Server-Objekte (z.B. Tabellen oder Spalten) verwendet werden sollen, die Zeichen wie Leerzeichen enthalten, oder legt diese fest QuoteSuffix Ruft das oder die Endzeichen ab, die beim Angeben von Namen für SQL Server-Objekte (z.B. Tabellen oder Spalten) verwendet werden sollen, die Zeichen wie Leerzeichen enthalten, oder legt diese fest Tabelle 5.26 Eigenschaften des CommandBuilder-Objektes 372 ADO.NET und Datenbanken CommandBuilder-Methoden Ein CommandBuilder-Objekt stellt folgende Methoden zur Verfügung: Name Beschreibung DeriveParameters Füllt die Parameter-Auflistung des angegebenen Command-Objekts mit den Parameterinformationen für eine in Command angegebene Gespeicherte Prozedur auf GetDeleteCommand Ruft das für Löschvorgänge in der Datenbank erforderliche, automatisch generierte Command-Objekt ab, wenn eine Anwendung Update für DataAdapter aufruft GetInsertCommand Ruft das für Einfügevorgänge in der Datenbank erforderliche, automatisch generierte Command-Objekt ab, wenn eine Anwendung Update für DataAdapter aufruft GetUpdateCommand Ruft das für Aktualisierungen in der Datenbank erforderliche, automatisch generierte Command-Objekt ab, wenn eine Anwendung Update für DataAdapter aufruft RefreshSchema Aktualisiert die Schemainformationen der Datenbank, die zum Generieren von INSERT-Anweisungen, UPDATEAnweisungen und DELETE-Anweisungen verwendet werden Tabelle 5.27 Methoden des CommandBuilder-Objektes 5.9.2 Aktualisieren von Autowerten Bei Verwendung des CommandBuilders in einer Anwendung, die es erlaubt, im DataSet neue Datensätze anzulegen, ist es notwendig, das Aktualisieren von Datentabellen mit automatischer Schlüsselwertvergabe genauer zu planen: 왘 Im DataSet und in der Datenbank müssen unterschiedliche Autowerte für neue Datensätze vergeben werden, die sich nicht überschneiden sollten. Am einfachsten ist dies über die Definition von negativen Autowerten für Schlüsselfelder im DataSet zu erreichen (siehe auch das Beispiel in Abschnitt 7.2.4, »Webdienst zur Aktualisierung der Datenbank«). 왘 Beim Einfügen der neuen Datensätze sollten in der Datenbank neu vergebene Autowerte auch in das DataSet der Anwendung über- Datenauswahl und Datenaktualisierung 373 tragen werden, um dort mit den aktualisierten Schlüsselwerten weiter zu arbeiten. Der zuletzt von der Datenbank erzeugte Schlüsselwert kann mit Hilfe der Systemvariablen des SQL Servers @@IDENTITY, die auch in Access 2000 zur Verfügung steht, ausgelesen werden. Der SQL Server 2000 bietet alternativ den funktionsgleichen Aufruf scope_ identity, der sicherstellt, dass der erzeugte Schlüsselwert tatsächlich aus der Einfügeaktion innerhalb der eigenen Transaktion entstanden ist. @@identity und scope_identity() Für die erfolgreiche Aktualisierung des DataSets mit automatisch erzeugten Schlüsselwerten sind zwei Voraussetzungen erforderlich: 왘 Die INSERT-Anweisung muss mit einer zweiten Anweisung, die @@IDENTITY oder scope_identity abfragt, ergänzt werden. 왘 Die UpdatedRowSource-Eigenschaft des InsertCommand-Objektes muss auf FirstReturnedRecord gesetzt werden, damit die entsprechende Datenzeile des DataSets mit dem neuen Datenschlüssel aktualisiert wird. Listing 5.47 Vorbereitung zur Aktualisierung von Autowerten ' Pseudocode zur Verdeutlichung des Verfahrens _cmd = New SqlCommand(SELECT-Anweisung) ' DataAdapter initialisieren _da = New SqlDataAdapter(_cmd) _da.Fill(ds) ' Datenmanipulation in der Anwendung ' ... Try Dim _cb As New SqlCommandBuilder(_da) ' INSERT-, DELETE- und UPDATE-Anweisungen ' des CommandBuilders auslesen ' und ggf. nachbearbeiten _da.InsertCommand = _cb.GetInsertCommand _da.InsertCommand.CommandText = _da.InsertCommand.CommandText & "; " & _ "SELECT * FROM Tabelle " & _ "WHERE pkTabelle = @@IDENTITY" _da.InsertCommand.UpdatedRowSource = _ UpdateRowSource.FirstReturnedRecord 374 ADO.NET und Datenbanken _da.DeleteCommand = _cb.GetDeleteCommand _da.UpdateCommand = _cb.GetUpdateCommand _da.Update(ds) Catch ex As Exception ' ... End Try Die Aktualisierung von Autowerten im DataSet wird für SqlDataAdapter, die Sie mit dem Visual Studio .NET-Assistenten erstellen, in der beschriebenen Weise automatisch eingerichtet. Dies gilt nicht für OleDbDataAdapter, da nicht alle OleDB-Datenquellen eine Variable @@IDENTITY unterstützen. Wenn Sie Access 2000-Datenbanken verwenden oder über den OleDb-Datenprovider auf den SQL Server zugreifen, sollten Sie den Aktualisierungscode entsprechend manuell anpassen. 5.10 Datenbindung und Datennavigation Die Datenbindung in Masken dient dazu, Steuerelemente so mit Datenquellen zu verbinden, dass ausgewählte Inhalte der Datenquelle in diesen Steuerelementen angezeigt werden und Änderungen, die an Inhalten in den Steuerelementen vorgenommen werden, gegebenenfalls die Datenquelle aktualisieren. 5.10.1 BindingContext Datengebundene Steuerelemente sind keine Neuerung von Visual Basic .NET, sondern waren schon mit ADO(alt) in Visual Basic 6 verfügbar. Anders als in Visual Basic 6 ist kein separates ADODataControl zur Datenbindung und zur Navigation in den Datensätzen einer Datenquelle erforderlich, da Visual Studio .NET-Masken ein BindingContext-Objekt besitzen, das diese Aufgabe übernimmt. BindingContext Im .NET Framework ist eine Datenbindung an alle Objekte möglich, die wie Arrays, Collections oder ADO.NET-Datenobjekte eine IListSchnittstelle implementieren. Es spielt keine Rolle, ob Sie ein ADO.NET-Datenobjekt oder eine Objekt-Klasse als Datenquelle wählen. Im Prinzip kann jede Eigenschaft eines Steuerelement-Objektes an eine Datenquelle gebunden werden, was unter anderem eine direkte Steuerung des Erscheinungsbildes von Anwendungen durch Datenbankinhalte möglich macht. Datenbindung an Datenobjekte, Klassen und Arrays Datenbindung und Datennavigation 375 Es ist wichtig zu verstehen, dass, anders als in einer Recordset-Verarbeitung mit Visual Basic 6, in ADO.NET kein »aktueller« Datensatz einer Bearbeitungsschleife als Bezug für die Datenbindung mehrerer Steuerelemente zur Verfügung steht. Die Bindung von ausgewählten Steuerelementen an Inhalte aus der gleichen Datenquelle wird über ein BindingManagerBase-Objekt bestimmt, das aus dem BindingContext einer Maske abgeleitet ist. Die Navigation in den Datensätzen erfolgt über die Position-Eigenschaft des BindingContexts. Einfache und komplexe Datenbindungen Datenbindungen für einzelne Steuerelementinhalte einer Maske werden der Auflistung der Datenbindungen (DataBindings) durch deren Add-Methode hinzugefügt. Bei komplexen Datenbindungen wie z.B. dem Füllen eines Dropdowns aus einer Detaildatentabelle werden dessen Bindungen über ein eigenes CurrencyManager-Objekt verwaltet und angesprochen, auf das der BindingContext verweist. Listing 5.48 Beispiel für eine einfache Datenbindung txtGewürzname.DataBindings.Add("Text", _ dsDataSet.Tables("Gewürze"), "Gewürzname") txtGattung.DataBindings.Add("Text", _ dsDataSet.Tables("Gewürze"), "Gattung") Listing 5.49 Beispiel für eine komplexe Datenbindung DropDown.DataSource = dvDataView DropDown.ValueMember = "fkGewürz" DropDown.DisplayMember = "Bildname" DropDown.DataBindings.Add("SelectedValue", _ dsDataSet.Tables("Gewürze"), "pkGewürz") Beide Bindungstypen lassen sich Steuerelementen zur Entwurfszeit sehr leicht auch im Eigenschaftenfenster der Visual Studio .NET-IDE zuordnen (siehe Abbildung 5.38). 376 ADO.NET und Datenbanken Abbildung 5.38 Zuweisen der Datenbindung in der Visual Studio .NET-IDE Eigenschaften der BindingManagerBase Ein BindingManagerBase-Objekt hat folgende Eigenschaften: Name Beschreibung Bindings Ruft die Auflistung verwalteter Bindungen ab Count Ruft beim Überschreiben in einer abgeleiteten Klasse die Anzahl der Zeilen ab, die von BindingManagerBase verwaltet werden Current Ruft beim Überschreiben in einer abgeleiteten Klasse das aktuelle Objekt ab Position Ruft beim Überschreiben in einer abgeleiteten Klasse die Position in der zugrunde liegenden Liste ab, auf die an diese Datenquelle gebundene Steuerelemente zeigen, oder legt diese fest Tabelle 5.28 Eigenschaften des BindingManagerBase-Objekts Methoden der BindingManagerBase Das BindingManagerBase-Objekt stellt folgende Methoden zur Verfügung: Name Beschreibung AddNew() Fügt beim Überschreiben in einer abgeleiteten Klasse der zugrunde liegenden Liste ein neues Element hinzu Tabelle 5.29 Methoden des BindingManagerBase-Objekts Datenbindung und Datennavigation 377 Name Beschreibung CancelCurrentEdit() Bricht beim Überschreiben in einer abgeleiteten Klasse den aktuellen Bearbeitungsvorgang ab EndCurrentEdit() Beendet beim Überschreiben in einer abgeleiteten Klasse den aktuellen Bearbeitungsvorgang GetItemProperties() Ruft die Liste der Eigenschaftenbezeichner für die Datenquelle ab oder legt diese fest RemoveAt() Löscht beim Überschreiben in einer abgeleiteten Klasse die Zeile am angegebenen Index aus der zugrunde liegenden Liste ResumeBinding() Setzt beim Überschreiben in einer abgeleiteten Klasse die Datenbindung fort SuspendBinding() Unterbricht die Datenbindung beim Überschreiben in einer abgeleiteten Klasse Tabelle 5.29 Methoden des BindingManagerBase-Objekts (Forts.) Ereignisse der BindingManagerBase Das BindingManagerBase-Objekt löst folgende Ereignisse aus: Name Beschreibung CurrentChanged Tritt ein, wenn sich der gebundene Wert ändert PositionChanged Tritt ein, wenn sich Position geändert hat Tabelle 5.30 Ereignisse des BindingManagerBase-Objekts 5.10.2 Datenmaske mit Bildern Das Beispiel AccessFormular diente bereits in Abschnitt 5.3, »Datengebundene Eingabemaske«, zur Darstellung der Datenbindung und der Navigation in Datensätzen. Es soll an dieser Stelle erweitert werden und die Anzeige der Bilder ermöglichen, die den Gewürzen in der Datenbank dbWürzen zugeordnet sind. Dabei wird die FormatMethode des Binding-Objektes für das Bild-Steuerelement verwendet, um die Bilddaten aus der Datenbank zu laden. Ergänzend ist es in diesem Beispiel möglich, vorhandene Bilder zu ersetzen und damit neue Bilder in die Datenbank zu übertragen. 378 ADO.NET und Datenbanken Bilder aus der Datenbank Die Speicherung von Bildern in einer Datenbank galt mit ADO(alt) als abwegige, da brüchige Lösung, die in jedem Fall zu vermeiden war. In der Regel wurden zu Datensätzen gehörende Bilder als Dateien in eigenen Verzeichnissen gespeichert und in der Datenbank lediglich die Pfadnamen verwaltet. Im asynchronen Modell von ADO.NET und unter Berücksichtigung der Fähigkeiten des .NET Frameworks kann auch diese Regel in Frage gestellt werden. 왘 Nachdem .NET Framework-Anwendungen dynamisch aktualisierbar sind und auf mehreren Servern gleichzeitig mit den gleichen Daten laufen, stellt die Replikation von dateibasierten Bilddaten ein konzeptionelles Hindernis für flexible verteilte Anwendungen dar. 왘 Durch Verwendung der Klassen des .NET Frameworks verringert sich der Aufwand erheblich, Bilder in eine Datenbank zu schreiben und sie in einem Datenstrom wieder auszulesen. Siehe dazu auch das folgende Beispiel. 왘 Das Auslesen von Bildern in den Dateigrößen, wie sie z.B. in Webanwendungen Verwendung finden, verursacht mit aktuellen Datenbankservern keine Performance-Probleme. 왘 Mit ADO.NET können Sie auf verschiedene Datenbanktypen gleichzeitig zugreifen und deren Daten gemeinsam verarbeiten – es gibt daher mehrere, auch kombinierte Lösungen mit XMLDatenmengen, die eine Speicherung von Bildern in Einzeldateien überflüssig machen. Das Speichern von kleinen Bildern in der Datenbank ist, was die Handhabung, den Aufwand zur Aktualisierung, die Möglichkeiten zum Backup und die direkte Verfügbarkeit betrifft, mit ADO.NET deutlich gegenüber einer dateibasierten Lösung zu bevorzugen. Sie finden das fertige Beispiel AccessFormularBild auf der beiliegenden CD-ROM im Verzeichnis \Kapitel05\AccessFormularBild. Sie können auch, wie im Folgenden beschrieben, das Beispielprojekt AccessFormular fortsetzen. Datenbindung und Datennavigation Beispiel AccessFormularBild 379 Datenbindung einrichten Einige Datenbindungen für die Bilder- und Gewürztabellen sind bereits aus dem vom Assistenten erstellten Beispielprojekt AccessFormular vorhanden (siehe Listing 5.50). Listing 5.50 Vorhandene Datenbindung im Beispielprojekt AccessFormular Me.BindingContext(objdsWürzen, "tblGewürze") Me.BindingContext(objdsWürzen, "tblBilder") Fügen Sie in der Entwurfsansicht von DatenFormular aus dem Abschnitt Windows Forms der Toolbox ein PictureBox-Element PictureBox1 hinzu. Die Bindung für das PictureBox-Steuerelement soll beim Laden des Datenformulars gesetzt werden. Wählen Sie daher aus dem linken Codenavigations-Dropdown Basisklassenereignisse, dann aus dem rechten Load, um die Ereignisprozedur für das Laden von DatenFormular anzulegen. Format-Ereignis nutzen Tragen Sie in DatenFormular_Load eine einfache Datenbindung für die PictureBox1 an das Datenbankfeld tblBilder.Foto ein und definieren Sie zwei BindingManagerBase-Objekte für die vorhandenen Datenbindungen. Listing 5.51 DatenFormular_Load Private Sub DatenFormular_Load( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Datenbindung für die PictureBox einrichten PictureBox1.DataBindings.Add("Image", _ objdsWürzen, "tblBilder.Foto") ' Format-Ereignis zum Laden des Bildes einsetzen AddHandler PictureBox1.DataBindings("Image"). _ Format, AddressOf FotoFormatieren ' BindingManagerBase für Hauptdaten einrichten bmbGewürze = Me.BindingContext( _ objdsWürzen, "tblGewürze") ' BindingManagerBase für Detaildaten einrichten bmbBilder = Me.BindingContext( _ objdsWürzen, "tblBilder") End Sub 380 ADO.NET und Datenbanken Zur Übertragung der Bilddaten in die PictureBox1 wird im Beispiel das Format-Ereignis des Binding-Objektes genutzt. Dieses Ereignis tritt immer dann auf, wenn Daten aus der Datenquelle an ein Steuerelement gesendet und dabei umgewandelt bzw. formatiert werden. Mit Hilfe der Anweisung: AddHandler PictureBox1.DataBindings("Image"). _ Format, AddressOf FotoFormatieren wird der Formatierungsvorgang an eine eigene Prozedur FotoFormatieren delegiert. Binding-Ereignisse Ein Binding-Objekt kann folgende Ereignisse auslösen: Name Beschreibung Format Tritt ein, wenn die Eigenschaft eines Steuerelements an einen Datenwert gebunden ist Parse Tritt ein, wenn der Wert eines datengebundenen Steuerelements geändert wird Tabelle 5.31 Ereignisse des Binding-Objektes Binding-Eigenschaften Ein Binding-Objekt hat folgende Eigenschaften: Name Beschreibung BindingManagerBase Ruft die BindingManagerBase dieser Bindung ab BindingMemberInfo Ruft ein Objekt mit Informationen über die Bindung ab, die auf dem dataMember-Parameter im Binding-Konstruktor basieren Control Ruft das Steuerelement ab, zu dem die Bindung gehört DataSource Ruft die Datenquelle für diese Bindung ab Tabelle 5.32 Eigenschaften des Binding-Objektes Datenbindung und Datennavigation 381 Name Beschreibung IsBinding Ruft einen Wert ab, der angibt, ob die Bindung aktiv ist PropertyName Ruft den Namen der Eigenschaft zur Datenbindung des Steuerelements ab oder legt diesen fest Tabelle 5.32 Eigenschaften des Binding-Objektes (Forts.) FotoFormatieren Die Prozedur FotoFormatieren wird vom Ereignis Format des Binding-Objektes aufgerufen, wenn der aktuelle Datensatz über die Position des BindingContexts geändert wird und Bilddaten in die Picturebox1 geladen werden. Der Aufbau einer Prozedur zur Formatierung von Daten folgt einem einfachen Prinzip: Die Daten sind im Ereignis-Argument e enthalten, werden in der Prozedur umgewandelt, formatiert und an das EreignisArgument e wieder zurückgegeben, mit dem die formatierten Daten schließlich in das Steuerelement gelangen. Listing 5.52 FotoFormatieren Public Sub FotoFormatieren( _ ByVal sender As Object, _ ByVal e As ConvertEventArgs) ' Leeres Datenfeld abfangen If Not IsDBNull(e.Value) Then ' EventArgument enthält Byte-Array Dim _img As Byte() = CType(e.Value, Byte()) Dim _ms As New IO.MemoryStream() ' Offset für manche Access Blobs auf 78 setzen Dim _int As Integer = 0 ' Bytes in einen MemoryStream schreiben _ms.Write(_img, _int, _img.Length - _int) ' Bitmap aus MemoryStream erzeugen Dim _bmp As New Bitmap(_ms) _ms.Close() ' Bitmap an EventArgument zurückgeben e.Value = _bmp End If End Sub 382 ADO.NET und Datenbanken Die Bilddaten erreichen die Prozedur im Objekt e und können als Array von Bytes ausgelesen werden. Ziel der Prozedur ist es, dieses Byte-Array in ein Bitmap-Objekt umzuwandeln. Dazu bietet es sich an, die Bytes in einen MemoryStream zu schreiben und daraus ein Bitmap-Objekt zu erzeugen, das am Ende wieder an e übergeben wird. Die Methode, mit der die Bytes in einen MemoryStream geschrieben werden, akzeptiert die Argumente offset und count, mit denen gesteuert werden kann, bei welchem Byte die Übertragung in den Datenstrom beginnen soll und wie viele Bytes überhaupt übertragen werden sollen. Die Bilder aus der SQL Server- und der Access-Variante der Beispieldatenbank dbWürzen benötigen keine Angabe eines Offset-Wertes. Bei manchen Bildern, die in anderen Access-Datenbanken als OLE-Objekte eingefügt wurden, ist allerdings die Angabe von offset=78 erforderlich, um die Bilddaten von einem proprietären OLEDatenheader zu trennen. MemoryStream Falls die Write-Methode mit dem angegebenen Offset-Wert fehlschlägt, müssen Sie das Byte-Array eines Bildes bei der Übertragung analysieren. Setzen Sie dazu in der Zeile mit _ms.Write einen Haltepunkt und starten Sie die Anwendung im Debugmodus. Wenn der Haltepunkt erreicht ist, wählen Sie aus dem Kontextmenü von _img den Eintrag Schnellüberwachung. Der Beginn der eigentlichen Bitmapdaten ist an der Kennung BM zu erkennen, die als Bytes die Werte 66 77 hat. Passen Sie über die Variable _int den Offset-Wert mit der Position des ersten Bytes der Kennung an. Nach dem Start der Anwendung und dem Laden der Daten wird das erste Bild geladen. Bei einer Navigation in den Hauptdaten wechselt das Bild allerdings nicht, da den BindingManager-Objekten der beiden Tabellen noch die Synchronisation fehlt. Position synchronisieren Wählen Sie aus dem linken Codenavigations-Dropdown bmbGewürze, dann aus dem rechten PositionChanged, um die Ereignisprozedur für die Navigation in der Haupttabelle anzulegen. Datenbindung und Datennavigation 383 Abbildung 5.39 Anzeige von Bildern aus der Datenbank Navigationsereignis Position Changed Legen Sie fest, dass beim Wechsel der Position-Eigenschaft der Hauptdaten der BindingManager der Detaildaten auf dieselbe Position weist. Listing 5.53 Ereignisprozedur für den Wechsel der Position Private Sub bmb_PositionChanged( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles bmbGewürze.PositionChanged ' Aktuelles Bild anzeigen bmbBilder.Position = bmbGewürze.Position ' Bild ist dann vorhanden, wenn beide ' BindingManager auf denselben Datensatz zeigen If bmbGewürze.Current.row.Item("pkGewürz") <> _ bmbBilder.Current.row.Item("fkGewürz") Then PictureBox1.Visible = False Else PictureBox1.Visible = True End If End Sub Da nicht jedem Gewürz ein Bild zugeordnet ist, sollte die PictureBox1 nur dann sichtbar sein, wenn die BindingManager beider Tabellen auf denselben Datenschlüssel in der Hauptdatentabelle zeigen. 384 ADO.NET und Datenbanken Neues Bild laden Der Vollständigkeit halber folgt der Code zum Laden eines neuen Bildes, das durch Klick auf btnLaden ausgelöst wird. Fügen Sie der Maske in der Entwurfsansicht aus der Toolbox ein OpenFileDialog-Objekt hinzu, über dessen Filter-Eigenschaft Sie in der Code-Ansicht den zu wählenden Dateityp vorgeben können. Listing 5.54 Das Laden eines neuen Bildes über einen Datei-Öffnen-Dialog Private Sub btnBildLaden_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnBildLaden.Click ' Bitmap mit Dateidialog auswählen With OpenFileDialog1 .CheckFileExists = True .Filter = "Datendateien (*.jpg)|*.jpg" If .ShowDialog = DialogResult.OK Then ' Bitmap-Datei as Stream laden Dim _st As System.IO.Stream = .OpenFile Dim _img As New Bitmap(_st) PictureBox1.Visible = True PictureBox1.Image = _img End If End With End Sub Nach der erfolgreichen Auswahl einer Bilddatei wird diese von der OpenFile-Methode als Datenstrom geöffnet und daraus ein BitmapObjekt erzeugt. Die Zuweisung der Bitmap an das Steuerelement PictureBox1 fügt dieses über die Datenbindung auch in die Datentabelle ein und wird über Aktualisieren in die Datenbank übertragen. 5.11 Hierarchische Daten Wie in Abschnitt 5.8, »Tabellenverknüpfungen«, gezeigt wurde, ist es nicht schwer, ein DataGrid an verknüpfte Tabellen zu binden, um in diesen zu navigieren. Die Darstellung von Datenhierarchien in einem DataGrid ist jedoch sehr eingeschränkt, da jeweils nur die Daten einer Tabelle anzeigt werden und kein Überblick über alle untergeordneten Datensätze in einer Hauptdatentabelle möglich ist. Hierarchische Daten Verknüpfte Tabellen im DataGrid 385