Zusatzkapitel zum Buch Access 2010 - Das Grundlagenbuch für Entwickler www.access-entwicklerbuch.de/2010 1 ADO Im vorherigen Kapitel haben Sie bereits erfahren, dass DAO die bevorzugte DatenzugriffsBibliothek ist, wenn es um die Entwicklung reiner Access-An­wen­dun­gen geht. ADO (ActiveX Data Objects) war von Microsoft als Nach­folger von DAO geplant. Es eröffnet wesentlich mehr Mög­lich­keiten, die sich aber vor allem dann bemerkbar machen, wenn Sie ein alternatives Backend wie beispielsweise Microsoft SQL Server oder MySQL verwenden. Der Fokus dieses Buchs richtet sich auf die Entwicklung reiner Access-Daten­bank­an­ wen­dungen. Daher sollen die Mög­lichkeiten von Access in Zusammenarbeit mit dem SQL Server hier nicht betrachtet werden. Zusatzkapitel ADO Ent­wick­ler, die jetzt noch reine Access-Anwendungen ­pro­grammieren, aber diese im Hinblick auf einen spä­teren Wechsel auf den SQL Server direkt auf dieses Backend ­vorbereiten möchten, sollen natürlich nicht außen vor bleiben – zumal ADO auch für reine Access-An­wendungen einige Features bereithält, die DAO nicht bietet. BEISPIELDATENBANK Die Beispieldatei zu diesem Kapitel finden Sie unter dem Namen ADO.accdb auf www.acciu.de/aeb2010. Wegen des gegenüber DAO wesentlich größeren Funk­tions­umfangs könnte über ADO ein eigenes Buch geschrie­ ben werden. Aus Platzgründen wird das Thema hier je­ doch ein wenig eingeschränkt – und zwar so, dass Sie die Techniken, die Sie im vorherigen Kapitel über DAO ken­nen gelernt haben, auch mit ADO einsetzen können. Na­ türlich soll auch die eine oder andere Spezialität von ADO nicht unerwähnt bleiben. Viele bereits in »DAO« ab Seite 493 enthaltene Informationen gelten auch für den Um­gang mit ADO. Dies bezieht sich vor allem auf formale Techniken wie den Umgang mit Auflistungen, die Verwendung von Punkt oder Ausrufezeichen für den Bezug auf Elemente und Eigenschaften oder das De­klarieren und Instanzieren von Objekten. Wenn Sie Informationen zu diesen Themen be­nötigen, schlagen Sie am besten im oben ge­ nann­ten Kapitel nach. ADO-Neuigkeiten Mit Access 2010 kommt nach Access 2007 wiederum keine neue ADO-Version. Der mit Access 2007 eingeführte JET-OLE­DB-Provider namens Microsoft Office 12.0 Access Database Engine OLE DB Provider (Micro­soft.ACE.OLEDB.12.0) findet weiterhin Verwendung. Im Gegensatz zu DAO mit den Objekten Recordset2 und Field2 gibt es unter ADO keine Möglichkeit, die in Attachment- oder mehrwertigen Feldern gespeicherten Daten über ein zusätzliches Recordset auszulesen. Daher kann man nur über die »Unterfelder« auf die in den verknüpften, verborgenen Tabellen enthaltenen Daten zugreifen. Wie dies funktioniert, erfahren Sie unter »Daten eines Recordsets mit mehrwertigen Feldern ausgeben« auf Seite 992 und »Daten eines Recordsets mit Attachment-Feldern ausgeben« auf Seite 993. 1.1 Zugriff auf eine Datenquelle herstellen Obgleich Sie im Folgenden feststellen werden, dass viele Vorgehensweisen unter DAO und ADO gleich ablaufen, unterscheidet sich das Objektmodell von ADO in einigen Punk­ 978 Zugriff auf eine Datenquelle herstellen ten vom DAO-Objektmodell. Das macht sich beim Zugriff auf die gewünschte Da­ten­bank sofort bemerkbar, wie der folgende Abschnitt zeigen wird. Connection und ConnectionString Das beginnt damit, dass nicht das Database-Objekt, sondern das Connection-Objekt Ur­ sprung aller lesenden, schreibenden und sonstigen Zugriffe auf die Tabellen der Da­ten­ bank ist. Das Connection-Objekt enthält immer einen ConnectionString, der Informationen über die Verbindung zur gewünschten Datenbank enthält. Diesen ConnectionString stellt man entweder selbst zusammen oder man lässt sich dabei unterstützen. Die einfachste Methode dazu ergibt sich beim Zugriff auf die aktuelle Datenbank. In diesem Fall brauchen Sie einfach nur auf das Connection-Objekt des bestehenden CurrentProject-Objekts zuzugreifen. Public Sub Verbindung() Dim cnn As ADODB.Connection Set cnn = CurrentProject.Connection With cnn Debug.Print cnn.ConnectionString End With End Sub Listing 1.1: Verweisen auf eine Verbindung zur aktuellen Datenbank und Ausgabe der Connection­ String-Eigenschaft Die Verbindungszeichenfolge für die aktuelle Datenbank sieht etwa so aus – gut, dass Sie diesen Ausdruck nicht selbst zusammenstellen müssen: Provider=Microsoft.ACE.OLEDB.12.0Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=E:\ ADO.accdbmdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database=C:\ Dokumente und Einstellungen\Administrator\Anwendungsdaten\Microsoft\Access\System. mdw;Jet OLEDB:Registry Path= Software\Microsoft\Office\12.0\Access\Access Connectivity EngineSoftware\Microsoft\Office\11.0\Access\Jet\4.0;Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=65;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False;Jet OLEDB:Support Complex Data=True Was die Parameter im Einzelnen bedeuten, soll hier gar nicht aufgeschlüsselt werden. Für die Verwendung der Datenbank, zu der auch das VBA-Projekt gehört, reicht die Kenntnis, dass CurrentProject.Connection die richtige Verbindung liefert. Für den Fall, dass Sie einmal eine Verbindung zu einer externen Datenquelle herstellen möchten, brauchen Sie den ConnectionString auch nicht unbedingt manuell zusammen- 979 Zusatzkapitel ADO zustellen. Zur Ermittlung der passenden Parameter können Sie auch einen Dialog verwenden, der bei der Festlegung der Parameter behilflich ist. Die folgende Routine erzeugt eine Instanz des DataLinks-Objekts und zeigt mit der Methode PromptNew den Dialog zum Anlegen einer neuen Verbindungszeichenfolge an (siehe Abbildung 1.1). Public Function ConnectionStringErmitteln() ConnectionStringErmitteln = CreateObject("DataLinks").PromptNew End Function Listing 1.2: Aufruf des Dialogs zum Ermitteln einer Verbindungszeichenfolge Mit dem gleichen Dialog können Sie nicht nur die Eigenschaften neuer Verbindungen auswählen und zurückgeben lassen, sondern auch die Eigenschaften von CurrentProject. Connection etwas übersichtlicher ausgeben (siehe Abbildung 1.2). Dazu müssen Sie die aufrufende Routine geringfügig abändern: Public Function ConnectionStringBearbeiten() ConnectionStringBearbeiten = _ CreateObject("DataLinks").PromptEdit(CurrentProject.Connection) End Function Listing 1.3: Aufrufen des Dialogs Datenverknüpfungseigenschaften für eine bestehende Ver­ bindungs­zeichenfolge Abbildung 1.1: Anlegen einer neuen Verbindung 980 Manipulation des Datenmodells Abbildung 1.2: Bearbeiten von CurrentProject.Connection 1.2 Manipulation des Datenmodells Die Manipulation von Tabellen, Feldern und anderen Datenbankobjekten erfolgt nicht über ADO selbst, sondern über die Objekte, Methoden und Eigenschaften der Bibliothek ADOX. Die entsprechende Bibliothek im Verweise-Dialog heißt Microsoft ADO Ext. 2.8 for DDL and Security (oder älter). 1.2.1 Anlegen einer Tabelle Zum Anlegen einer Tabelle mit reinen ADO-Mitteln benötigen Sie das Catalog-Objekt der ADOX-Bibliothek (es ginge ja auch mit CurrentProject.Execute "CREATE TABLE..."). Dieses wird mit der Eigenschaft ActiveConnection auf die aktuelle Datenbank eingestellt. Zum Löschen einer eventuell schon vorhandenen gleichnamigen Tabelle stellt das CatalogObjekt die Delete-Methode der Tables-Auflistung zur Verfügung. Anschließend wird ein neues Table-Objekt erstellt und mit dem Namen tblUnternehmen versehen. Bevor die Tabelle an die Tables-Auflistung angehängt und damit verfügbar gemacht wird, fügen Sie zwei Felder an: UnternehmenID und Unternehmen. Dabei werden zwei unterschiedliche Vorgehensweisen verwendet: Die erste instanziert zunächst ein neues Column-Objekt, füllt dessen Eigenschaften mit den entsprechenden Daten und fügt es dann an die Columns-Auflistung des Table-Objekts an. Die zweite übergibt die benötigten Informationen direkt beim Anfügen eines neuen Feldes an das Table-Objekt. Nach dem Anlegen werden die Tables-Auflistung und der Navigationsbereich aktualisiert. 981 Zusatzkapitel ADO Public Sub TabelleAnlegen_Unternehmen() Dim cat As ADOX.Catalog Dim tbl As ADOX.Table Dim col As ADOX.Column Set cat = New ADOX.Catalog cat.ActiveConnection = CurrentProject.Connection 'Bestehende Tabelle löschen cat.Tables.Delete "tblUnternehmen" 'Verweis auf neues Table-Objekt Set tbl = New ADOX.Table 'Name der Tabelle zuweisen tbl.Name = "tblUnternehmen" 'Feld neu erstellen und per Objektvariable referenzieren Set col = New ADOX.Column col.Name = "UnternehmenID" col.Type = adInteger tbl.Columns.Append col 'Noch ein Feld erstellen, kurze Fassung tbl.Columns.Append "Unternehmen", adVarWChar, 30 'Tabelle anhängen und Katalog aktualisieren With cat.Tables .Append tbl .Refresh End With 'Navigationsbereich aktualisieren Application.RefreshDatabaseWindow Set tbl = Nothing Set cat = Nothing End Sub Listing 1.4: Anlegen einer Tabelle mit ADOX Konstanten für Datentypen unter ADO und ADOX Tabelle 1.1 zeigt die häufigsten ADOX-Konstanten für die unterschiedlichen Daten­typen. Konstante Datentyp adBigInt Big Integer adBinary Binary adBoolean Boolean adUnsignedTinyInt Byte adChar Char 982 Manipulation des Datenmodells Konstante Datentyp adCurrency Currency adDate Date/Time adNumeric Decimal adDouble Double adGUID GUID adSmallInt Integer adInteger Long adLongVarBinary Long Binary (OLE Object), Attachment.FileData adLongVarWChar Memo adNumeric Numeric adSingle Single adWChar, adVarWChar Text (Unicode) adDBTime Time adIDispatch Attachment, ComplexTypes adDBTimeStamp Time Stamp Tabelle 1.1: Konstanten für den Datentyp 1.2.2 Autowert anlegen Wenn Sie das Feld UnternehmenID als Autowert festlegen möchten, müssen Sie in obiger Routine noch einige Zeilen hinter dem Anlegen des Feldes hinzufügen: 'Anlegen eines Autowertes With tbl.Columns("UnternehmenID") .ParentCatalog = cat .Properties("AutoIncrement") = True End With Die Eigenschaft AutoIncrement gilt nur für Access-Datenbanken. 1.2.3 Löschen einer Tabelle Das Löschen einer Tabelle erfolgt über die Delete-Methode der Tables-Auflistung. Die folgende Routine löscht die soeben erstellte Tabelle und aktualisiert die Tables-Auflistung sowie den Navigationsbereich. Public Sub TabelleLoeschen_Unternehmen() Dim cat As ADOX.Catalog Set cat = New ADOX.Catalog 983 Zusatzkapitel ADO cat.ActiveConnection = CurrentProject.Connection 'Bestehende Tabelle löschen On Error Resume Next cat.Tables.Delete "tblUnternehmen" On Error GoTo 0 'Katalog aktualisieren cat.Tables.Refresh 'Navigationsbereich aktualisieren Application.RefreshDatabaseWindow Set cat = Nothing End Sub Listing 1.5: Löschen einer Tabelle mit ADOX 1.2.4 Erstellen eines Indexes Mit der folgenden Routine fügen Sie der Tabelle tblUnternehmen einen Primärindex auf dem Feld UnternehmenID hinzu. Public Sub IndexErstellen() Dim cat As ADOX.Catalog Dim idx As ADOX.Index Dim tbl As ADOX.Table Dim col As ADOX.Column Dim idxs As ADOX.Indexes 'Catalog instanzieren und auf aktuelle Datenbank einstellen Set cat = New ADOX.Catalog cat.ActiveConnection = CurrentProject.Connection 'Tabelle festlegen Set tbl = cat.Tables("tblUnternehmen") 'Verweis auf Indexes-Auflistung erstellen Set idxs = tbl.Indexes 'Neues Index-Objekt erstellen Set idx = New ADOX.Index With idx 'Index-Objekt mit Eigenschaften ausstatten .Name = "PrimaryKey" .PrimaryKey = True .Unique = True 'Column-Objekt mit zu indizierendem Feld erzeugen 'und zur Auflistung der indizierten Columns hinzufügen Set col = New ADOX.Column 984 Manipulation des Datenmodells col.Name = "UnternehmenID" .Columns.Append col End With 'Index an die Auslistung Indexes anfügen idxs.Append idx Set col = Nothing Set idx = Nothing Set idxs = Nothing Set tbl = Nothing Set cat = Nothing End Sub Listing 1.6: Anlegen eines Index mit ADOX 1.2.5 Löschen eines Indexes Zum Entfernen eines Indexes verwenden Sie die folgende Routine. Sie setzt die DeleteMethode der Indexes-Auflistung zum Entfernen des Indexes ein. Public Sub IndexLoeschen() Dim cat As ADOX.Catalog Dim tbl As ADOX.Table Dim idxs As ADOX.Indexes Set cat = New ADOX.Catalog cat.ActiveConnection = CurrentProject.Connection Set tbl = cat.Tables("tblUnternehmen") Set idxs = tbl.Indexes 'Index aus der Auflistung löschen idxs.Delete "PrimaryKey" Set tbl = Nothing Set idxs = Nothing Set cat = Nothing End Sub Listing 1.7: Löschen eines Indexes aus der Auflistung der Indizes einer Tabelle 1.2.6 Erstellen einer Beziehung Um eine Beziehung zwischen zwei Tabellen herzustellen, verwenden Sie die folgende Routine. Voraussetzung ist, dass das Fremdschlüsselfeld der Detailtabelle den gleichen Daten­ typ wie das Primärschlüsselfeld der Mastertabelle hat. Außerdem muss der Primär­ 985 Zusatzkapitel ADO schlüssel der Mastertabelle eindeutig sein. Sind die Voraussetzungen erfüllt (und die angegebenen Tabellen beziehungsweise Felder vorhanden), legt die Routine die Beziehung aus Abbildung 1.3 an. Wenn Sie zusätzlich Löschweitergabe oder Aktualisierungsweitergaben definieren möchten, müssen Sie die Eigenschaften DeleteRule und UpdateRule des key-Objekts mit den entsprechenden Werten bestücken. Mit der Konstanten adRICascade sorgen Sie für die Weitergabe der jeweiligen Aktion: Public Sub BeziehungErstellen() Dim cat As ADOX.Catalog Dim tbl As ADOX.Table Dim key As ADOX.key Set cat = New ADOX.Catalog cat.ActiveConnection = CurrentProject.Connection 'Tabelle mit dem Fremdschlüsselfeld festlegen Set tbl = cat.Tables("tblMitarbeiter") 'Neuen Key instanzieren und Eigenschaften zuweisen Set key = New ADOX.key key.Name = "ForeignKey" key.Type = adKeyForeign 'Tabelle mit Primärschlüssel festlegen key.RelatedTable = "tblUnternehmen" 'Verknüpfungsfeld der Detailtabelle angeben key.Columns.Append "UnternehmenID" 'Optional: Lösch- oder Aktualisierungsweitergabe key.DeleteRule = adRICascade key.UpdateRule = adRICascade 'Verknüpfungsfeld der Mastertabelle angeben key.Columns("UnternehmenID").RelatedColumn = "UnternehmenID" 'Key an die Keys-Auflistung anhängen tbl.keys.Append key Set key = Nothing Set tbl = Nothing Set cat = Nothing End Sub Listing 1.8: Anlegen einer Beziehung zwischen zwei Tabellen 986 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten Abbildung 1.3: Mit ADOX erstellte Beziehung 1.2.7 Löschen einer Beziehung Die Beziehung beziehungsweise den Fremdschlüssel löschen Sie mit der folgenden Routine: Public Sub BeziehungLoeschen() Dim cat As ADOX.Catalog Set cat = New ADOX.Catalog cat.ActiveConnection = CurrentProject.Connection 'Beziehung in Form des Fremdschlüssels löschen cat.Tables("tblMitarbeiter").keys.Delete "ForeignKey" Set cat = Nothing End Sub Listing 1.9: Löschen einer Beziehung 1.3 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten In den folgenden Abschnitten erfahren Sie, wie Sie auf die Daten der Datenbank zugreifen können. 1.3.1 Ausgeben aller Tabellen Im vorigen Kapitel zum Thema DAO haben Sie erfahren, wie Sie mit DAO alle Tabellen ausgeben oder prüfen, ob eine Tabelle vorhanden ist. Unter ADO erledigen Sie dies wie in folgendem Beispiel: Sub DBInfoADO() Dim rs As New ADODB.Recordset Dim fld As ADODB.Field Set rs = CurrentProject.Connection.OpenSchema(adSchemaTables) Do While Not rs.EOF 987 Zusatzkapitel ADO For Each fld In rs.Fields Debug.Print , fld.Name, "=", fld.Value Next fld rs.MoveNext Debug.Print Loop rs.Close End Sub Das liefert beispielsweise solche Informationen: TABLE_CATALOG = Null TABLE_SCHEMA = Null TABLE_NAME = tblDateien TABLE_TYPE = TABLE TABLE_GUID = Null DESCRIPTION = Null TABLE_PROPID = Null DATE_CREATED = 21.01.2007 14:52:08 DATE_MODIFIED = 21.01.2007 14:52:11 Wenn Sie nur die benutzerdefinierten Tabellen ausgeben möchten, erledigen Sie das etwa so: Sub DBInfoADO() Dim rst As New ADODB.Recordset Dim fld As ADODB.Field Set rst = CurrentProject.Connection.OpenSchema(adSchemaTables) Debug.Print "Tabellen:" Do While Not rst.EOF For Each fld In rst.Fields If fld.Name = "TABLE_NAME" And rst("TABLE_TYPE") = "TABLE" Then Debug.Print fld.Value End If Next fld rst.MoveNext Loop rst.Close End Sub Es gibt noch einige weitere Konstanten, die Sie der OpenSchema-Methode übergeben können, zum Beispiel die Folgenden – experimentieren Sie einfach mal damit: »» adSchemaColumns 988 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten »» adSchemaConstraintColumnUsage »» adSchemaForeignKeys »» adSchemaIndexes »» adSchemaKeyColumnUsage »» adSchemaPrimaryKeys »» adSchemaProcedures und adSchemaViews (Abfragen) »» adSchemaProviderTypes »» adSchemaReferentialConstraints »» adSchemaStatistics (Beziehungen) »» adSchemaTableConstraints »» adSchemaTrustees (DB-Benutzer) 1.3.2 Prüfen, ob eine Tabelle vorhanden ist Auch hier brauchen Sie nicht mit ADO zu arbeiten, sondern können die AllTablesAuflistung verwenden: Public Function IstTabelleVorhanden_ADO(strTabellenname As String) _ As Boolean Dim objTable As AccessObject On Error Resume Next Set objTable = CurrentData.AllTables (strTabellenname) IstTabelleVorhanden_ADO = Not objTable Is Nothing End Function Listing 1.10: Prüfen des Vorhandenseins einer Tabelle 1.3.3 Datensatzgruppe auf Basis einer Tabelle öffnen Das Öffnen einer Datensatzgruppe erfolgt anders als unter DAO. Die Methode zum Öffnen ist keine Methode des übergeordneten Objekts (bei DAO das Database-Objekt), sondern eine Methode des ADO-Recordset-Objekts selbst. Die Open-Methode erwartet den Namen der zu öffnenden Tabelle oder Abfrage beziehungsweise einen SQLAusdruck, den Connection-String sowie zwei Parameter zur Angabe des Cursor-Typs und des Sperrmechanismus. Die Beschreibung der verschiedenen Möglichkeiten finden Sie im Anschluss an die Routine zum Öffnen einer Datensatzgruppe. Hier tritt offen zu Tage, weshalb es sich lohnt, bei der Variablendeklaration explizit die Bibliothek mit 989 Zusatzkapitel ADO anzugeben, aus der die Objekte stammen: Wenn Sie das versäumt haben und die DAOBibliothek ist in der Liste der Verweise oberhalb der ADO-Bibliothek angeordnet, sucht die folgende Routine vergeblich die Open-Methode des Recordset-Objekts. Public Sub DatensatzgruppeOeffnen() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "tblUnternehmen", cnn, adOpenDynamic, adLockOptimistic With rst 'etwas mit der Datenatzgruppe machen End With rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.11: Öffnen einer Datensatzgruppe mit ADO Das eigentliche Öffnen der Datensatzgruppe kann auch so erfolgen, wobei zunächst die Eigenschaften festgelegt werden und erst dann die Da­ten­satz­grup­pe geöffnet wird: rst.ActiveConnection = cnn rst.LockType = adLockOptimistic rst.CursorType = adOpenKeyset rst.Open "tblUnternehmen" 1.3.4 Cursor-Typen Beim Öffnen einer Datensatzgruppe unter ADO können Sie folgende Cursor-Typen für die Eigenschaft CursorType verwenden: »» a dOpenDynamic: Liefert eine Gruppe von Datensätzen und zeigt Daten­satz­än­de­run­ gen an­derer Benutzer an (entspricht dbOpenDynaset unter DAO). »» a dOpenForwardOnly: Liefert einen Snapshot der gewünschten Datensätze zum Zeit­ punkt des Öffnens des Recordset-Objekts, kann nur vorwärts durchlaufen werden (ent­spricht dbOpenForwardOnly unter DAO). »» a dOpenKeyset: Liefert Verweise auf die Datensätze der zugrunde liegenden Tabellen (ent­spricht keiner DAO-Konstante genau, ist aber fast äquivalent zu dbOpenDynaset und etwas schneller als adOpenDynamic). 990 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten »» a dOpenStatic: Liefert einen Snapshot der gewünschten Datensätze zum Zeitpunkt des Öffnens des Recordset-Objekts (entspricht dbOpenSnapshot unter DAO). Achtung: Bei Recordsets, die mit Connections geöffnet werden, die nicht CurrentProject entsprechen, müssen Sie zusätzlich die CursorLocation auf den Wert adUseClient festlegen: cnn.CursorLocation = adUseClient 1.3.5 Sperrung von Daten Mit dem Parameter LockType legen Sie fest, wie die Daten beim Schreiben gesperrt werden sollen: »» adLockReadonly: Öffnet ein schreibgeschütztes Recordset. »» a dLockPessimistic: Sperrt die komplette Speicherseite, in der sich der von einer Än­ de­rung betroffene Datensatz befindet, sobald die Bearbeitung beginnt. »» a dLockOptimistic: Sperrt die komplette Speicherseite, in der sich der von einer Än­de­ rung betroffene Datensatz befindet, erst, wenn der Datensatz aktualisiert wird. »» adLockBatchOptimistic: Wie adLockOptimistic, aber für die UpdateBatch-Methode. 1.3.6 Datensätze eines Recordsets durchlaufen Zum Durchlaufen der Datensätze eines Recordsets verwenden Sie beispielsweise die Do While-Schleife, in der Sie nach dem Durchführen der gewünschten Aktion jeweils mit der MoveNext-Methode einen Datensatz weiter springen. Als Abbruchbedingung dient die EOF-Eigenschaft der Datensatzgruppe, die den Wert True erhält, wenn der Datensatzzeiger über den letzten Datensatz hinaus verschoben wurde. Public Sub DatensaetzeDurchlaufen() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "tblUnternehmen", cnn, adOpenKeyset, adLockOptimistic Do While Not rst.EOF With rst 'etwas mit dem aktuellen Datensatz tun End With rst.MoveNext Loop rst.Close 991 Zusatzkapitel ADO Set rst = Nothing Set cnn = Nothing End Sub Listing 1.12: Datensatzgruppe durchlaufen Das Pendant zur EOF-Eigenschaft ist die BOF-Eigenschaft. Sie erhält den Wert True, wenn der Datensatzzeiger sich vor dem ersten Datensatz der Datensatzgruppe befindet. Neben der MoveNext-Methode gibt es noch die Methoden MoveFirst, MoveLast und MovePrevious zum Bewegen innerhalb der Datensatzgruppe. 1.3.7 Daten eines Recordsets mit mehrwertigen Feldern ausgeben ADO bietet keine besondere Möglichkeit, um auf die Inhalte mehrwertiger Felder zuzugreifen – es gibt also kein Recordset2- oder Field2-Objekt wie unter DAO. Daher können Sie nur auf die in solchen Feldern enthaltenen Daten zugreifen, wenn Sie diese in der Datenherkunft über die »Unterfelder« referenzieren, wie das folgende Beispiel zeigt: Public Sub DatensaetzeDurchlaufen_MehrwertigesFeld() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "SELECT MehrwertigesFeld.Value " _ & "FROM tblMehrwertigesFeld", _ cnn, adOpenKeyset, adLockOptimistic Do While Not rst.EOF With rst Debug.Print rst.Fields("MehrwertigesFeld.Value") End With rst.MoveNext Loop rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.13: Zugriff auf mehrwertige Felder mit einem ADO-Recordset 992 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten 1.3.8 Daten eines Recordsets mit Attachment-Feldern ausgeben Der Umgang mit den in Attachment-Feldern gespeicherten Daten sieht prinzipiell wie bei den mehrwertigen Feldern aus: Public Sub DatensaetzeDurchlaufen_Attachment() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "SELECT DateiID, Datei.FileType, " _ & "Datei.Filename FROM tblDateien", _ cnn, adOpenKeyset, adLockOptimistic Do While Not rst.EOF With rst Debug.Print rst!DateiID, rst.Fields("Datei.FileType"), _ rst.Fields("Datei.Filename") End With rst.MoveNext Loop rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.14: Zugriff auf Attachment-Felder 1.3.9 Anzahl der Datensätze in einer Datensatzgruppe ermitteln Um die Anzahl der Datensätze zu ermitteln, dürfen Sie nicht die Konstante adOpenFor­ ward­Only für die Eigenschaft CursorType einsetzen. Außerdem müssen Sie für die Ei­gen­ schaft CursorLocation den Parameter adUseClient verwenden, da serverseitige Da­ten­­ satz-Cur­sor unter ACE das Zählen von Datensätzen nicht unterstützen. Public Sub Datensatzanzahl() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.CursorLocation = adUseClient 993 Zusatzkapitel ADO rst.CursorType = adOpenForwardOnly rst.LockType = adLockOptimistic rst.ActiveConnection = cnn rst.Open "tblUnternehmen" Debug.Print rst.RecordCount End Sub Listing 1.15: Ermitteln der Datensatzanzahl eines Recordsets 1.3.10 Prüfen, ob eine Datensatzgruppe leer ist Eine einfache Möglichkeit, um herauszufinden, ob eine Datensatzgruppe leer ist, besteht im Prüfen der EOF- und der BOF-Eigenschaften der Datensatzgruppe: Public Sub LeereDatensatzgruppe() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "tblMitarbeiter", cnn, adOpenForwardOnly, adLockOptimistic If rst.BOF And rst.EOF Then MsgBox "Die Datensatzgruppe ist leer." End If rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.16: Prüfen, ob eine Datensatzgruppe leer ist 1.3.11 Ausgabe des Inhalts eines Recordsets Manchmal möchte man den Inhalt eines Recordsets auf die Schnelle betrachten oder etwa in einer Textdatei speichern. Dabei leistet die GetString-Methode gute Dienste. Diese Methode ist sehr flexibel und kann, mit zusätzlichen Parametern ausgestattet, die Spalten auch als formatierten Text zurückgeben. Informationen zu den (optionalen) Parametern finden Sie unter http://msdn.microsoft. com/en-us/library/ms676975%28v=vs.85%29.aspx. Public Sub DatensatzgruppeAusgeben() … rst.Open "tblUnternehmen", cnn, adOpenForwardOnly, adLockOptimistic 994 Zugriff auf Tabellen, Abfragen und die darin enthaltenen Daten Debug.Print rst.GetString … End Sub Listing 1.17: Ausgeben der Daten einer Datensatzgruppe 1.3.12 Speichern der Daten in einem Array Wenn Sie die Daten einer Datensatzgruppe in einem Array weiter verarbeiten möchten, können Sie mit der GetRows-Methode ein zweidimensionales Array mit den in der Datensatzgruppe enthaltenen Daten füllen. Die GetRows-Methode hat drei Parameter: Rows gibt an, wie viele Datensätze eingelesen werden sollen, Start enthält ein Bookmark auf den ersten einzulesenden Datensatz und Fields die Position oder den Namen des einzulesenden Feldes beziehungsweise ein Array mit den Namen der einzulesenden Felder. Ohne die Angabe von Parametern liest GetRows alle Datensätze mit allen Feldern ein. Public Sub DatensatzgruppeInArray() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Dim varRecordset() As Variant Dim i As Integer Dim j As Integer Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "tblUnternehmen", cnn, adOpenForwardOnly, adLockOptimistic varRecordset = rst.GetRows() For i = 0 To UBound(varRecordset, 2) For j = 0 To UBound(varRecordset, 1) Debug.Print varRecordset(j, i) Next j Next i rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.18: Einlesen der Daten einer Datensatzgruppe in ein Array 995 Zusatzkapitel ADO 1.3.13 Abfragen mit Parametern verwenden Während Sie unter DAO das QueryDef-Objekt und die Parameters-Auflistung verwendet haben, um Abfragen mit Parametern zu handhaben, stellt ADO für diesen Zweck das Command-Objekt zur Verfügung. Das folgende Beispiel zeigt, wie Sie ein Recordset auf Basis einer mit Parametern versehenen Abfrage öffnen. Sie verwenden hier nicht wie bei DAO eine Parameters-Auflistung, sondern geben den Wert eines einzelnen Parameters als String und mehrere Werte in der richtigen Reihenfolge als String-Array an – bei anderen Datentypen für die Parameter verwenden Sie dann eher ein Variant-Array. Public Sub Parameterabfrage() Dim cnn As ADODB.Connection Dim cmd As ADODB.Command Dim rst As ADODB.Recordset Dim lngRecordsAffected As Long Set cnn = CurrentProject.Connection 'Command-Objekt instanzieren Set cmd = New ADODB.Command 'Aktuelle Verbindung zuweisen cmd.ActiveConnection = cnn 'auszuführende Abfrage angeben cmd.CommandText = "qryMitarbeiter" 'Typ des Commands festlegen cmd.CommandType = adCmdTable 'Command-Objekt ausführen. Der erste Parameter wird nur für 'Aktionsabfragen benötigt, der zweite enthält die Werte für die 'Abfrageparameter. Ein Parameter wird als String, mehrere als 'Array übergeben. Set rst = cmd.Execute(, "Minhorst") Do While Not rst.EOF Debug.Print rst!Vorname, rst!Nachname rst.MoveNext Loop Set rst = Nothing Set cnn = Nothing End Sub Listing 1.19: Erzeugen eines Recordsets auf Basis einer Parameterabfrage 996 Datensätze suchen 1.4 Datensätze suchen Zur Suche von Datensätzen gibt es mehrere Möglichkeiten. Wie unter DAO können Sie indizierte Felder mit der Seek-Methode durchsuchen, anderenfalls hilft die Find-Me­ thode weiter. Diese ist allerdings nicht so flexibel wie die Find…-Methoden von DAO, wie Sie nachfolgend lesen können. Am einfachsten ist es jedoch, die gesuchten Da­ten­sätze direkt in der Datenherkunft des Recordset-Objekts einzugrenzen. 1.4.1 Gesuchte Datensätze per Source-Eigenschaft des Recordsets ermitteln Die Source-Eigenschaft eines Recordset-Objekts enthält die dem Recordset-Objekt zugrunde liegende Tabelle oder Abfrage. Sie können hier auf drei Arten bereits mit dem Öffnen des Recordset-Objekts die gewünschten Daten ausfindig machen: »» Direkte Angabe einer SELECT-Anweisung (jeweils in einer Zeile): rst.Open "SELECT * FROM Artikel WHERE Artikelname LIKE 'A%'", cnn, adOpenKeyset, adLockOptimistic »» Angabe einer gespeicherten Abfrage: rst.Open "qryArtikelMitPreisGroesser50", cnn, adOpenKeyset, adLockOptimistic »» Kombination aus SELECT-Anweisung und gespeicherter Abfrage: rst.Open "SELECT * FROM qryArtikelMitPreisGroesser50 WHERE Artikelname LIKE 'T%'", cnn, adOpenKeyset, adLockOptimisticJoker in Zeichenketten unter ADO und SQL Wenn Sie wie in den obigen Beispielen Vergleichsausdrücke mit Platzhaltern verwenden möchten, müssen Sie die SQL Server-Syntax verwenden. Dabei entspricht der Platzhalter für beliebig viele Zeichen dem Prozentzeichen (%) und nicht wie in VBA oder Abfragen, die Sie über die Abfrageentwurfsansicht erstellen (siehe Abbildung 1.4), dem Sternchen (*). Der Platzhalter für ein einzelnes Zeichen entspricht dem Unterstrich (_) und nicht wie in VBA oder Abfragen dem Fragezeichen (?). 997 Zusatzkapitel ADO Abbildung 1.4: Joker für beliebig viele Zeichen in Abfragen 1.4.2 Seek Wenn Sie mit der Seek-Methode nach Daten suchen möchten, müssen zwei Bedingungen erfüllt sein: Das zu durchsuchende Feld muss indiziert sein und Sie müssen die Konstante adCmdTableDirect als Option beim Öffnen der Datensatzgruppe festlegen. Ersteres prüfen Sie ganz einfach, indem Sie in der Entwurfsansicht einer Tabelle den Indizes-Dialog einblenden (Ribbon-Eintrag Entwurf|Einblenden/Ausblenden|Indizes, siehe Abbildung 1.5). Letzteres impliziert, dass Sie nur auf einzelne Tabellen, aber nicht auf Abfragen oder verknüpfte Tabellen zugreifen können. Abbildung 1.5: Anzeigen der Indizes einer Tabelle 998 Datensätze suchen Die Option adcmdTableDirect verwenden Sie mit der Open-Methode des RecordsetObjekts. Die folgende Routine zeigt, wie Sie mit der Seek-Methode einen bestimmten Datensatz einer Tabelle finden und den Wert seines Primärschlüsselfeldes ausgeben. Im Gegensatz zur weiter unten vorgestellten Find-Methode enthält das Recordset-Objekt nicht alle Datensätze, die dem Suchkriterium entsprechen, sondern es wird lediglich der Datensatzzeiger auf einem Datensatz platziert, der den mit der Seek-Methode übergebenen Parametern entspricht. Dabei gibt es verschiedene Varianten, die Sie mit dem Parameter SeekOption übergeben. Die wichtigsten sind folgende: »» a dSeekFirstEQ: Setzt den Datensatzzeiger auf den ersten Datensatz mit dem angegebenen Wert. »» a dSeekLastEQ: Setzt den Datensatzzeiger auf den letzten Datensatz mit dem angegebenen Wert. Seek ist unter den gegebenen Bedingungen die schnellste Möglichkeit, um auf einen bestimmten Datensatz zuzugreifen. Public Sub SuchenMitSeek() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset 'Recordset mit direktem Zugriff auf die Tabelle öffnen rst.Open "Artikel", cnn, adOpenKeyset, adLockOptimistic, _ adCmdTableDirect 'Index festlegen: Achtung, Indexname und nicht den Feldnamen verwenden! rst.Index = "Artikelname" 'Suche starten rst.Seek "Chocolade", adSeekFirstEQ 'Aktuellen Datensatz anzeigen If Not rst.EOF Then Debug.Print rst![Artikel-Nr] End If rst.Close Set rst = Nothing Set cnn = Nothing End Sub Listing 1.20: Suche mit der Seek-Methode 999 Zusatzkapitel ADO 1.4.3 Find Die Find-Methode ist weniger flexibel als die Find…-Methoden von DAO. Sie fasst zwar die Funktion der Find…-Methoden von DAO zusammen, beschränkt allerdings beispielsweise die möglichen Kriterien auf ein einziges. Die Find-Methode bietet vier Parameter, von denen nur die Angabe des Suchkriteriums Pflicht ist. Als Suchkriterium dient ein aus Feldname, Vergleichsoperator und Ver­ gleichs­ wert zusammengesetzter Ausdruck, wobei die gleichen Regeln wie für die WHERE-Klau­sel von SQL-Abfragen gelten (siehe auch »Access-SQL« ab Seite 453). Die übrigen Pa­rameter: »» S kipRecords legt fest, wie viele Datensätze von der aktuellen Position aus übersprungen werden sollen, und ist beispielsweise wichtig, wenn Sie bereits einen Datensatz ge­funden haben und bei der Suche des nächsten Datensatzes nicht auf dem aktuellen Datensatz stehen bleiben möchten. »» S earchDirection erwartet eine der Konstanten adSearchForward oder adSearchBack­ ward und sucht in der entsprechenden Richtung. »» S tart erwartet die Position des Datensatzzeigers für einen vom ersten Datensatz abweichenden Startpunkt. Wenn die Find-Methode einen Datensatz findet, positioniert sie den Datensatzzeiger auf dem gefundenen Datensatz. Sollen weitere Datensätze gefunden werden, wie etwa im folgenden Beispiel, verwenden Sie innerhalb einer Do While-Schleife erneut die FindMe­thode – mit dem gleichen Kriterium, aber dem Wert 1 für den Parameter SkipRecords. Dies hat den Grund, dass die Find-Methode mit der Suche immer in dem Datensatz be­ ginnt, auf dem sich aktuell der Datensatzzeiger befindet. Eine solche Suche bedingt immer die Prüfung auf die Eigenschaft EOF oder BOF der Da­tensatzgruppe (je nach Suchrichtung), da dies die Position des Datensatzzeigers ist, wenn kein Datensatz gefunden wurde. Das entspricht dann der Eigenschaft Recordset. No­Match unter DAO. Public Sub SuchenMitFind() Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset Dim strKriterium As String Set cnn = CurrentProject.Connection Set rst = New ADODB.Recordset rst.Open "Artikel", cnn, adOpenKeyset, adLockOptimistic strKriterium = "[Artikelname] LIKE 'A*'" rst.Find strKriterium Do While Not rst.EOF 1000 Datensätze suchen Debug.Print rst!Artikelname rst.Find strKriterium, 1 Loop … End Sub Listing 1.21: Suche nach allen Datensätzen, deren Artikelname mit A beginnt 1.4.4 Filtern Genau wie DAO enthält das Recordset-Objekt auch unter ADO eine Eigenschaft namens Filter. Unter DAO weist man einem Recordset-Objekt den Filter zu und erhält das gefilterte Ergebnis, wenn man ein weiteres Recordset-Objekt auf Basis des ersten öffnet. Dies ist unter ADO einfacher: Hier weisen Sie einfach das gewünschte Filterkriterium zu und können direkt im gleichen Recordset auf das gefilterte Ergebnis zugreifen. Public Sub FilternEinesRecordset() … rst.Open "Artikel", cnn, adOpenKeyset, adLockOptimistic rst.Filter = "Artikelname LIKE 'A*'" Do While Not rst.EOF Debug.Print rst!Artikelname rst.MoveNext Loop … End Sub Listing 1.22: Filtern aller Datensätze, deren Artikelname mit A beginnt Um den Filter zu entfernen, verwenden Sie die Konstante adFilterNone an Stelle eines Parameters: rst.Filter = adFilterNone Filter unterliegen unter ADO einigen Einschränkungen. Mehr dazu unter folgendem Link: http://msdn.microsoft.com/en-us/library/ms676691%28v=VS.85%29.aspx 1.4.5 Sortieren Die Datensätze eines Recordsets können Sie entweder bereits über die Datenherkunft oder durch nachträgliches Hinzufügen eines Sortierkriteriums sortieren. In der Da­ten­ her­kunft gibt es zwei Möglichkeiten: »» S ie öffnen die Datensatzgruppe mit der Option adTableDirect. In diesem Fall kommt die im Dialog Indizes angegebene Sortierung zur Anwendung. 1001 Zusatzkapitel ADO »» S ie öffnen eine gespeicherte Abfrage mit voreingestellter Sortierreihenfolge oder geben einen SQL-Ausdruck mit einem entsprechenden Sortierkriterium an. Sortieren nach dem Öffnen des Recordsets Wie in DAO bietet auch ADO eine Eigenschaft zum Angeben eines Sortierkriteriums. Voraussetzung für seinen Einsatz ist, dass das Recordset-Objekt einen clientseitigen Cur­sor verwendet. Wie auch die Filter-Eigenschaft wirkt sich die Sort-Eigenschaft unmittelbar auf die aktuelle Datensatzgruppe aus. Nach dem Öffnen des Recordsets können Sie eine Sortierung mit der Sort-Eigenschaft vornehmen: Public Sub SortierenEinesRecordset() … rst.CursorLocation = adUseClient rst.Open "Artikel", cnn, adOpenDynamic, adLockOptimistic rst.Sort = "Artikelname ASC" Do While Not rst.EOF Debug.Print rst!Artikelname rst.MoveNext Loop … End Sub Listing 1.23: Nachträgliches Sortieren einer Datensatzgruppe 1.4.6 Lesezeichen Auch ADO-Recordsets bieten eine Bookmark-Eigenschaft. Bookmarks dienen dazu, sich die Position eines Datensatzes zu merken und auf einen gemerkten Datensatz zurückzuspringen. Daher ist diese Eigenschaft les- und schreibbar. Unter Verwendung eines Bookmarks können Sie beispielsweise von einem bestimmten Datensatz an das Ende der Datensatzgruppe und anschließend wieder zurück zum ursprünglichen Datensatz springen. Public Sub SpringenMitBookmark() … Dim varLesezeichen As Variant … rst.Open "Artikel", cnn, adOpenKeyset, adLockOptimistic rst.Find "Artikelname = 'Chai'" Debug.Print rst!Artikelname 1002 Datensätze bearbeiten varLesezeichen = rst.Bookmark rst.MoveLast Debug.Print rst!Artikelname rst.Bookmark = varLesezeichen Debug.Print rst!Artikelname … End Sub Listing 1.24: Hin- und herspringen mit Bookmarks 1.5 Datensätze bearbeiten Daten lassen sich mit ADO auf verschiedene Art manipulieren. Nachfolgend erfahren Sie, wie Sie Daten mit Aktionsabfragen und mit den Methoden des Recordset-Objekts bearbeiten. 1.5.1 Datensatz anlegen Zum Anlegen eines neuen Datensatzes verwenden Sie AddNew- und die UpdateMethode. Dazwischen stellen Sie die Felder der Datensatzgruppe auf die gewünschten Werte ein. Public Sub DatensatzAnlegen() … rst.Open "tblUnternehmen", cnn, adOpenKeyset, adLockOptimistic rst.AddNew rst!Unternehmen = "Pearson Education Deutschland GmbH" rst.Update … End Sub Listing 1.25: Neuen Datensatz anlegen Sie brauchen im Unterschied zu DAO die Update-Methode nicht auszuführen, wenn Sie den Datensatz wechseln, bevor Sie das Recordset-Objekt schließen. Wenn Sie nach dem An­legen beispielsweise sofort noch einen weiteren Datensatz anlegen möchten, brauchen Sie die Update-Anweisung nur nach dem Anlegen des zweiten Datensatzes und vor dem Schließen der Datensatzgruppe aufzurufen: rst.AddNew rst!Unternehmen = "Pearson Education Deutschland GmbH" rst.AddNew 1003 Zusatzkapitel ADO rst!Unternehmen = "amisoft" rst.Update Wenn Sie mit AddNew das Anlegen eines neuen Datensatzes starten und das RecordsetOb­jekt schließen, bevor Sie die Update-Methode ausgeführt haben, lösen Sie einen Lauf­ zeit­fehler aus: rst.AddNew rst!Unternehmen = "Pearson Education Deutschland GmbH" 'Schließen ohne Update löst Laufzeitfehler aus rst.Close 1.5.2 Datensatz bearbeiten Während Sie unter DAO vor dem Bearbeiten eines Datensatzes die Edit-Methode aufrufen mussten, können Sie unter ADO Änderungen am aktuellen Datensatz direkt vornehmen. Die Änderungen übernehmen Sie schließlich mit Update. Public Sub DatensatzAendern() … rst.Open "tblUnternehmen", cnn, adOpenKeyset, adLockOptimistic rst.Find "Unternehmen = 'Pearson Education Deutschland GmbH'" rst!Unternehmen = "Addison Wesley" rst.Update … End Sub Listing 1.26: Datensatz ändern 1.5.3 Datensatz löschen Zum Löschen eines Datensatzes verschieben Sie den Datensatzzeiger auf den zu löschenden Datensatz und entfernen diesen mit der Delete-Methode. Public Sub DatensatzLoeschen() … rst.Open "tblUnternehmen", cnn, adOpenKeyset, adLockOptimistic rst.Find "Unternehmen = 'Pearson Education Deutschland GmbH'" rst.Delete … End Sub Listing 1.27: Löschen eines Datensatzes 1004 Transaktionen 1.5.4 Aktionsabfragen ausführen Aktionsabfragen führen Sie unter ADO mit der Execute-Methode des Connection-Objekts aus. Diese Methode erwartet als ersten Parameter den auszuführenden SQL-Ausdruck. Als zweiten Parameter können Sie eine Variable angeben, in der die Anzahl der durch die Aktionsabfrage betroffenen Datensätze gespeichert wird. Diese können Sie nachher weiter verwenden. Public Sub AktionsabfrageAusfuehren() Dim cnn As ADODB.Connection Dim cmd As ADODB.Command Dim lngRecordsAffected Set cnn = CurrentProject.Connection cnn.Execute "INSERT INTO tblUnternehmen(Unternehmen) " _ & "VALUES('Addison-Wesley')", lngRecordsAffected Debug.Print lngRecordsAffected Set cnn = Nothing End Sub Listing 1.28: Ausführen einer Aktionsabfrage 1.6 Transaktionen Transaktionen funktionieren unter ADO prinzipiell wie unter DAO. Der wichtigste Unterschied ist, dass die Bezeichnungen der drei Methoden zum Durchführen von Transaktionen vereinheitlicht wurden. Diese heißen jetzt: »» BeginTrans »» CommitTrans »» RollbackTrans Außerdem gehören die Methoden zum Connection-Objekt – unter DAO war es das Work­ space-Objekt. Weitere Informationen zu Transaktionen finden Sie in »Transaktionen« auf Seite 553. 1.7 Besonderheiten von ADO gegenüber DAO ADO bietet einige Besonderheiten gegenüber DAO. So ist es möglich, eine Da­ten­satz­ grup­pe zu speichern, Recordset-Objekte ohne Datenherkunft in Form einer Tabelle oder 1005 Zusatzkapitel ADO Ab­frage zu verwenden oder Recordsets von der Datenherkunft zu trennen und anschließend wieder zu verbinden. 1.7.1 Datensatzgruppe speichern Sie können eine Datensatzgruppe in einem Microsoft-eigenen Format oder im XMLFor­mat speichern, solange diese keine Anlagen oder mehrwertigen Felder enthält. Die folgende Routine speichert den Inhalt der Tabelle Personal in der Da­tei Personal.xml im Verzeichnis der Datenbank. Public Sub DatensatzgruppeSpeichern() … rst.Open "Personal", cnn, adOpenStatic, adLockOptimistic rst.Save CurrentProject.Path & "\Personal.xml", adPersistXML … End Sub Listing 1.29: Speichern einer Datensatzgruppe 1.7.2 Datensatzgruppe laden Um die so gespeicherte Datensatzgruppe wieder verfügbar zu machen, verwenden Sie die Open-Methode des Recordset-Objekts. Allerdings geben Sie statt eines Tabellenoder Abfragenamens den Namen der Datei an. Public Sub DatensatzgruppeEinlesen() Dim rst As New ADODB.Recordset rst.Open CurrentProject.Path & "\Personal.xml", , adOpenStatic, _ adLockOptimistic, adCmdFile Debug.Print rst.RecordCount Set rst = Nothing End Sub Listing 1.30: Einlesen einer Datensatzgruppe aus einer XML-Datei 1.7.3 Ungebundene Recordsets/temporäre Datensatzgruppen verwenden Unter ADO lassen sich Recordsets ohne Angabe einer Datenherkunft anlegen und zum Speichern von Daten verwenden – also ohne ein Connection-Objekt. Da ein ungebundenes Recordset keine Datenherkunft hat, besitzt es natürlich auch noch keine Felder. Diese können Sie ganz einfach mit der Append-Methode der Fields-Auflistung des Recordset-Objekts hinzufügen. 1006 Besonderheiten von ADO gegenüber DAO Anschließend können Sie das Recordset ganz normal verwenden. Ein ungebundenes Recordset ist beispielsweise sehr nützlich, wenn Sie größere Datenmengen in Kombinationsfeldern, Listenfeldern oder sogar Datenblättern anzeigen möchten, diese aber nicht in einer Tabelle gespeichert werden sollen. Die Routine aus dem folgenden Beispiel legt eine Datensatzgruppe mit den beiden Feldern ModulID und Modulname an und fügt alle Module der aktuellen Datenbank hinzu. Anschließend gibt sie alle Einträge der Tabelle im Direktfenster aus. Public Sub UngebundeneDatensatzgruppe() Dim rst As ADODB.Recordset Dim objModul As AccessObject Dim i As Integer Set rst = New ADODB.Recordset rst.Fields.Append "ModulID", adInteger rst.Fields.Append "Modulname", adVarWChar, 255 rst.Open For Each objModul In CurrentProject.AllModules rst.AddNew rst!ModulID = i rst!Modulname = objModul.Name rst.Update Next objModul rst.Update rst.MoveFirst Do While Not rst.EOF Debug.Print rst!ModulID, rst!Modulname rst.MoveNext Loop rst.Close Set rst = Nothing End Sub Listing 1.31: Anlegen einer ungebundenen Datensatzgruppe 1.7.4 Disconnected Recordsets Ein Vorteil von ADO gegenüber DAO ist, dass man ADO-Recordsets auf Basis einer Tabelle erstellen und diese dann »disconnecten« kann – was nichts anderes heißt, als dass man ihre Eigenschaft ActiveConnection auf den Wert Nothing setzt. Die Verbindung zur Tabelle ist damit unterbrochen, Änderungen am Recordset werden nicht direkt in die Tabelle übertragen. 1007 Zusatzkapitel ADO Wichtig ist bei dieser Technik, dass Sie das Recordset-Objekt so deklarieren, dass es auch nach dem Einlesen der Daten noch verfügbar ist – also nicht innerhalb der Routine, die das Recordset füllt. Am einfachsten erledigen Sie dies mit einer globalen Variablen, die Sie in einem Standardmodul deklarieren: Public rstDisconnected As ADODB.Recordset Es gibt im Wesentlichen zwei Einsatzgebiete für Disconnected Recordsets: nur lesende sowie schreibende und lesende. Bei den nur lesenden Einsätzen gilt es, oft benötigte und nicht zu ändernde Daten nur einmal einzulesen und immer wieder auf diese kopierten Werte zuzugreifen. Interessant ist dies vor allem, wenn die Daten über das Netzwerk oder gar das Internet beschafft werden müssen. Beispiele für solche Einsatzmöglichkeiten sind Konfigurationsdaten, Texte für Mel­ dungs­fenster oder Fehlermeldungen oder VBA- oder SQL-Ausdrücke wie Funk­tio­nen und Abfragen. Der zweite Grund für den Einsatz von Disconnected Recordsets sind Daten, die man vor einer (zwangsweisen) Trennung von der Datenherkunft einliest, bearbeitet und anschließend wieder speichert. Da Verbindungen teuer sind, was die Performance angeht, kann es durchaus Sinn machen, diese bei Bedarf zu trennen, um Ressourcen freizugeben. Disconnected Recordset zum Lesen öffnen Das erste Beispiel zeigt das Einlesen von Daten, auf die nur lesender Zugriff erfolgt: Public rstDisconnectedRead As ADODB.Recordset Public Sub DisconnectedRecordsetsRead() Set rstDisconnectedRead = New ADODB.Recordset With rstDisconnectedRead .ActiveConnection = CurrentProject.Connection .CursorType = adOpenStatic .CursorLocation = adUseClient .LockType = adLockReadOnly .Source = "SELECT * FROM tblMitarbeiter" .Open , , , , adCmdText If Not rstDisconnectedRead Is Nothing Then Set .ActiveConnection = Nothing End If End With End Sub Listing 1.32: Einlesen von Daten in ein Disconnected Recordset 1008 Besonderheiten von ADO gegenüber DAO Aus performancetechnischen Gründen können Sie hier den CursorType auf adOpenStatic und den LockType auf adLockReadOnly einstellen. Der Zugriff auf die im Recordset rstDisconnectedRead enthaltenen Daten erfolgt genau wie zu Beginn dieses Kapitels beschrieben. Disconnected Recordset einlesen, ändern und zurückschreiben Das zweite Beispiel zeigt, wie Sie ein Recordset öffnen, es von der Datenherkunft trennen, den Inhalt ändern und das Recordset wieder in die ursprüngliche Tabelle zurückschreiben. Mit der folgenden einfachen Routine füllen Sie das Recordset und trennen es von der Datenherkunft: Public Sub DisconnectedRecordsets() Set rstDisconnected = New ADODB.Recordset With rstDisconnected .ActiveConnection = CurrentProject.Connection .CursorType = adOpenDynamic .CursorLocation = adUseClient .LockType = adLockBatchOptimistic .Source = "SELECT * FROM tblMitarbeiter" .Open , , , , adCmdText If Not rstDisconnected Is Nothing Then Set .ActiveConnection = Nothing End If End With End Sub Listing 1.33: Füllen und »disconnecten« eines Recordset-Objekts Das Recordset ist nun »disconnected«; das heißt, dass Sie unabhängig von der Da­ten­ her­kunft Änderungen darin vornehmen können und diese nicht direkt dort übernommen werden. Eine einfache Änderung nehmen Sie mit folgender Routine vor: Public Sub DisconnectedRecordsetUpdaten() If Not rstDisconnected Is Nothing Then With rstDisconnected .Find "Nachname = 'Minhorst'" If Not .EOF = True Then !Vorname = "Andree" .Update End If End With 1009 Zusatzkapitel ADO End If End Sub Listing 1.34: Ändern des Inhalts eines Disconnected Recordsets Schauen Sie in der zugrunde liegenden Tabelle nach – der im Recordset geänderte Da­ten­satz enthält immer noch denselben Wert wie zuvor. Das ändern Sie nun, indem Sie das Re­­cordset wieder »connecten«. Wichtig ist dabei der Aufruf der Methode UpdateBatch zum Schreiben der Änderungen in die Tabelle – damit speichern Sie alle Änderungen an den enthaltenen Datensätzen: Public Sub DisconnectedRecordsetWiederVerbinden() If Not rstDisconnected Is Nothing Then With rstDisconnected .ActiveConnection = CurrentProject.Connection .UpdateBatch End With End If rstDisconnected.Close Set rstDisconnected = Nothing End Sub Listing 1.35: Verbinden des Disconnected Recordsets mit der zugrunde liegenden Datenbank und durchführen der Änderungen 1.7.5 Ereignisse von Datensatzgruppen Ein sehr interessantes Feature von ADO sind die Ereignisse der einzelnen Objekte wie Connection- oder Recordset-Objekt (siehe Abbildung 1.6). Diese sind gerade in Zu­sam­ men­hang mit der Programmierung mehrschichtiger Anwendungen interessant. 1010 Besonderheiten von ADO gegenüber DAO Abbildung 1.6: Ereigniseigenschaften von Recordset-Objekten Sie können die Ereignisse des Connection- und des Recordset-Objekts in einer eigenen Klas­se kapseln und entsprechende Prozeduren hinzufügen, die durch die jeweiligen Er­ eignisse ausgelöst werden. Die komplette Kapselklasse soll hier nicht abgedruckt werden, Sie finden diese aber in der Beispieldatenbank ADO.accdb im Klassenmodul clsADORS. Die folgende Routine instanziert diese Klasse und löst durch einige Datensatzoperationen verschiedene Ereignisse des Connection- und des Recordset-Objekts aus. Public Sub TestADOEvents() Dim cADO As New clsADORS cADO.SetSQL "SELECT * FROM Personal" DoEvents cADO.MoveRS 2 DoEvents cADO.SetSQL "SELECT * FROM Lieferanten" DoEvents cADO.MoveRS 7 DoEvents Set cADO = Nothing End Sub Listing 1.36: Instanzieren und Verwenden einer Klasse, die Connection- und Recordset-Ereig­ nisse auslöst Dies jedoch nur als Hinweis darauf, dass ADO durchaus Eigenschaften besitzt, die es von DAO positiv abheben. 1011 Zusatzkapitel ADO In der Beispieldatenbank ADOEvents.accdb finden Sie ein an ein ADO-Recordset gebundenes Formular, das die bei der Arbeit mit dem Recordset im Formular zusätzlich verfügbaren Ereignisse des Con­nec­tion- und des Recordset-Objekts erkennen lässt. BEISPIELDATENBANK Die Beispieldateien zu diesem Abschnitt finden Sie unter dem Namen ADOEvents. accdb auf www.acciu.de/aeb2010. 1012