Home > Artikel > Ausgabe 8/2015 > ADODB als Alternative zu DAO

ADODB als Alternative zu DAO

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Der Umgang mit Daten unter VBA findet in der Regel über die Bibliothek DAO statt, welche Access über den gleichnamigen Verweis beim Erzeugen einer neuen Datenbank automatisch in das VBA-Projekt einbindet. Microsoft hat sie damit zum Standard erkoren. Tatsächlich gibt es aber noch ein anderes Schwergewicht für den Datenzugriff, das unter dem Kürzel ADODB daherkommt. Führen wir uns diese Bibliothek einmal als Alternative zu DAO zu Gemüte.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1508_ADO.zip.

Datenzugriffsbibliotheken

Die Zeiten ändern sich. War DAO (Data Access Objects) als Schnittstelle zu den Datenobjekten unter VBA mit dem Erscheinen von Access noch die einzige Bibliothek, so führte Microsoft mit Office 2000 eine neue ein, die die alte zunehmend ersetzen und ihr angeblich in vielerlei Hinsicht überlegen sein sollte. Die Bezeichnung lautete ADODB als Abkürzung für das etwas schräge Microsoft ActiveX Data Objects Data Base.

Ein Hintergrund dafür war die Einführung der Access-Projekte, die den unmittelbaren Zugriff auf Objekte eines verbundenen SQL-Servers erlaubten und sogar deren Verwaltung ermöglichen. Derlei war mit DAO unmöglich, und so nahm man sich der bereits für Visual Basic vorliegenden Implementation von ADO an und pfropfte sie Access auf.

Statt DAO wurde beim Neuanlegen einer Datenbank automatisch der Verweis auf die neue Bibliothek gesetzt. Das änderte indessen nichts daran, dass die Access-Objekte, wie Formulare und Berichte, unter der Haube weiterhin mit DAO arbeiteten.

Diese Inkonsistenz ist neben der Tatsache, dass die Programmierung über ADO deutlich aufwändiger und komplizierter ist, mit ein Grund dafür, dass diese Technik von Entwicklern nicht wirklich angenommen wurde.

So elegant sie bei der Einbindung von Daten auf SQL-Servern ist, so überflüssig beim Zugriff auf die Tabellen der lokalen Access-Datenbank oder eines verknüpften Backends.

Microsoft wurde sich dessen offenbar bewusst, weshalb ab Access 2007 die Umkehr stattfand. Nun sollte DAO nicht nur für JET-Datenbanken, sondern sogar für die Ansprache von SQL-Servern über ODBC eingesetzt werden. Dafür dürfte allerdings nicht nur Einsicht Vater des Gedankens gewesen sein, sondern auch der Fakt, dass die Technik der Access-Projekte nicht mehr weiterentwickelt wurde und nach und nach dem Rotstift anheimfiel.

Auch, wenn ADO mittlerweile ein Stiefkind zu sein scheint, kann es nicht schaden, sich die Datenzugriffsbibliothek einmal genauer anzusehen. Denn sie verfügt über einige interessante Features, die DAO fehlen, weshalb ihr Einsatz in manchen Fällen doch noch sinnvoll sein kann.

ADODB im VBA-Projekt

Den Verweis auf die ADO-Bibliothek legt Access, wie erwähnt, nicht mehr selbst an, wenn Sie eine neue Datenbank erzeugen. Sie müssen Sie selbst einbinden. Dazu öffnen Sie im VBA-Editor über Extras|Verweise... den Verweise-Dialog und klappern dessen Einträge nach der Microsoft ActiveX Data Objects 2.x Library ab. Sie werden gleich mehrere davon finden, die die Versionsnummern 2.1 bis 2.8 tragen. Tatsächlich handelt es sich dabei aber lediglich um verschiedene Typbibliotheken für den gleichen Satz an DLLs, also das gleiche System, die aus Kompatibilitätsgründen angelegt sind. Aktivieren Sie einfach die höchste Version 2.8. Nach dem Schließen des Verweise-Dialogs finden Sie die Bibliothek im VBA-Objektkatalog, wenn Sie im Kombinationsfeld links oben den Eintrag ADODB auswählen.

Die Struktur von ADO

Schon am Umfang der Klassen der Bibliothek ist ersichtlich, dass deren Programmierung etwas komplexer ist, als unter DAO. Um die Bedeutung der einzelnen Objekte verständlich zu machen, ist es nützlich, sie in einen Vergleich zu jenen der DAO-Bibliothek zu setzen. In Bild 1 sind die wichtigsten Objekte gegenübergestellt.

Die Datenzugriffsbibliotheken ADO und DAO in der logischen Gegenüberstellung

Bild 1: Die Datenzugriffsbibliotheken ADO und DAO in der logischen Gegenüberstellung

Während Sie unter DAO auf die aktuelle Datenbank (Database-Objekt) unmittelbaren Zugriff haben, indem Sie die Funktion CurrentDb des Access-Application-Objekts aufrufen, oder die Methode OpenDatabase des ebenfalls bereits vorliegenden DbEngine-Objekts, muss in ADO erst eine Verbindung zur Datenbank aufgebaut werden.

Das ist auch schon der Hauptunterschied: Access ist beim Öffnen einer Datenbank ganz unbemerkt bereits mit der JET-Engine verbandelt. Die darüber liegende Schicht, die Treiber der Access Database Engine (ACE) samt ihrer dateispezifischen ISAM-Treiber, bleibt Ihnen verborgen. Verwendet werden dabei die Treiber für MDB- und ACCDB-Dateien. Neben diesen gibt es ja etwa auch noch jene zum Öffnen von Excel-, Text- oder DBase-Dateien. Um diese Schicht brauchen Sie sich nicht zu kümmern – die Integration vollzieht Access selbst. Anders unter ADO: Hier muss zunächst dezidiert der sogenannte Provider angegeben werden.

Dabei handelt es sich Imgrunde um einen im System registrierten Treiber, der zum Öffnen einer Datei herangezogen werden soll. Diese Provider werden von Office installiert, teilweise aber auch von Windows oder anderen Anwendungen. Alle Provider sind namentlich gekennzeichnet. So lautet der Name des Providers zum Öffnen einer Access-Datenbank aktuell etwa Microsoft.ACE.OLEDB.12.0. Früher nahm man zum Ansprechen einer MDB-Datei den Jet-Provider Microsoft.Jet.OLEDB.4.0. Soll ein MS-SQL-Server verbunden werden, dann heißt der Provider SQLOLEDB.1. Und schließlich kommen weitere DBMS-Server über ODBC ins Spiel, wenn der MSDASQL.1-Provider verwendet wird. Wie die jeweiligen Provider lauten, lässt sich aus Access heraus nicht ermitteln. Greifen Sie im Zweifelsfall zu einer Dokumentation im Internet, wie etwa der Domain connectionsstrings.com. Dort klicken Sie auf das gewünschte das Datenbanksystem, um die Verbindungszeichenfolgen zu erfahren. Die Seite unterscheidet dabei zwischen Zeichenfolgen für ODBC, OLEDB und .NET. Uns interessieren an dieser Stelle nur die OLEDB-Angaben, ein Synonym für ADODB.

Neben dem Namen des Providers finden Sie dort auch noch weitere Angaben, die Sie zum Provider machen können, nämlich die Optionen-Strings, welche die Treiber steuern. Doch dazu später mehr.

Haben Sie die Angaben zum Provider beisammen, so können Sie sich an das Anlegen eines Connection-Objekts machen. ADO kennt kein Database-Objekt, sondern nur das Verbindungsobjekt Connection, welches diesem aber in der Funktionalität ähnelt. Die Verbindung wird über die Methode Open hergestellt. Steht diese, so kann analog zur Database ein Recordset auf die Datenbankobjekte geöffnet werden. Nur lautet die hier verwendete Methode nicht OpenRecordset, sondern Execute. In beiden Fällen wird ein Recordset zurückgegeben.

Allerdings ist ein ADO-Recordset nicht dasselbe, wie ein DAO-Recordset! Zwar gleichen sich dessen Methoden weitgehend, doch es handelt sich dennoch um zwei unterschiedliche Klassen. Wenn Sie Objektvariablen zu diesen Recordsets deklarieren, so geben Sie deshalb ausdrücklich die Bibliothek an, aus dem das Recordset stammen soll:

Dim rs As DAO.Recordset

Dim rs As ADODB.Recordset

Das beugt Verwirrung vor, denn im VBA-Projekt können Sie auf beide Bibliotheken zugleich verweisen!

Die Unterschiede zwischen dem einen und dem anderen Recordset sind marginal. Beide weisen die üblichen Methoden zur Navigation (MoveNext, MoveLast, etc.) auf und haben jeweils eine Fields-Auflistung, über die auf die einzelnen Felder des Ergebnisses zugegriffen werden kann.

Connection anlegen

Gehen wir nun Schritt für Schritt vor, um an die Daten einer Datenbank über ADO zu gelangen.

Dass Access kein ADO-Pendant zu CurrentDb enthält, stimmt nicht ganz. Seit Einführung von ADODB gibt es das CurrentProject-Objekt. Dessen Eigenschaft Connection stellt die ADO-Connection zur aktuellen Datenbank dar. Sie können es direkt verwenden, um ein ADO-Recordset zu öffnen:

Dim con As ADODB.Connection

Dim rs As ADODB.Recordset

Set con = _

    CurrentProject.Connection

Set rs = _

   con.Execute( _

   "SELECT * FROM tblKunden")

Debug.Print rs(3).Value

Hier wird zunächst mit der Connection-Variablen con auf das Connection-Object von CurrentProject verwiesen. Das Recordset wird über die Execute-Anweisung der Connection gefüllt, welcher der gewünschte SQL-Ausdruck übergeben wird. Im Beispiel ist das der komplette Inhalt der Tabelle tblKunden. Schließlich wird das Feld Nummer 3 und dessen Wert abgefragt. (Die 3 in Klammern ist die abgekürzte Syntax für rs.Fields(3).Value, da die Fields-Auslistung die Standardeigenschaft eines ADO-Recordsets ist.)

Zum Vergleich die analoge Routine in der DAO-Version:

Dim dbs As Database

Dim rs As DAO.Recordset

Set dbs = CurrentDb

Set rs = dbs.OpenRecordset( _

     "SELECT * FROM tblKunden")

Debug.Print rs(3).Value

Wenn Sie nicht die aktuelle Datenbank ansprechen möchten, sondern eine andere Access-Datei, so muss ein Connection-Objekt von Grund auf neu erzeugt werden:

Dim con As ADODB.Connection

Set con = New ADODB.Connection

con.Provider = _

"Microsoft.ACE.OLEDB.12.0"

Über die New-Anweisung wird ein neues Connection-Objekt angelegt. Damit es weiß, welche Treiber zur Anwendung kommen sollen, wird der Provider für ACE-Datenbanken angegeben. Damit ist die oberste Schicht klargestellt.

Doch woher weiß die Connection, welches das Objekt der Begierde, die Datenbankdatei, ist? Die Angabe des Pfads muss in der Eigenschaft ConnectionString untergebracht werden:

con.ConnectionString = _

"Data Source=c:\xyz\test.accdb;"

Der ConnectionString ist der Ort, wo alle Optionen zur Verbindung eingestellt werden. Im Falle einer ACE-Datenbank, also einer MDB oder ACCDB, lautet dabei die Option für den Zugriffspfad Data Source. Bei anderen Providern könnte dieser Ausdruck auch einen anderen Namen haben.

Zusätzlich lassen sich weitere Optionen durch Semikola getrennt im String einbauen:

"Data Source=c:\datenbanken\test.accdb;Mode=Share Deny None;"

Mit Mode wird bestimmt, dass der Datenzugriff ohne Mehrbenutzersperren erfolgen soll. Und selbst der Provider kann hier untergebracht werden, was das Setzen in der Eigenschaft .Provider überflüssig macht:

"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\datenbanken\test.accdb;Mode=Share Deny None;"

Um eine Vorstellung zu bekommen, welche weiteren Optionen gesetzt sein können, setzen Sie diesen Ausdruck im VBA-Direktfenster ab:

CurrentProject.Connection. _

ConnectionString

Auf eine Abbildung des Ergebnisses wird hier verzichtet – Access setzt Unmengen ACE-spezifischer Verbindungsoptionen.

Sind alle nötigen Angaben im ConnectionString gemacht, so kann es an das physische Herstellen der Verbindung gehen, welches die Open-Methode bewirkt:

con.Open

Tritt kein Fehler bei dieser Anweisung auf, so ist die Verbindung geöffnet und bereit zum Abfragen oder Schreiben von Daten über die Execute-Methode, wie im Recordset-Beispiel oben.

ADO-Routinen

Da der Schreibaufwand bei ADO-Programmierung etwas höher ist, als bei DAO, empfiehlt es sich, für grundlegende Aufgaben ein separates VBA-Modul anzulegen. In der Beispieldatenbank nennt dieses sich mdlADODB. Im Folgenden sind einige Abschnitte des Moduls dargestellt.

MyConnectString (Listing 1) ist eine Hilfsfunktion, die eine gültige Verbindungszeichenfolge zurückgibt. Sie beschränkt sich auf das Zusammensetzen des ConnectionStrings über String-Verkettung. Neben dem Provider wird der Pfad zur anzusprechenden Access-Datenbank (1508_Backend.accdb) im Verzeichnis der aktuellen Datenbank (CurrentProject.Path) hier hartkodiert eingebaut. So eine Hilfsfunktion ist unter Umständen sinnvoll, damit der String nicht an mehreren Stellen im Code neu aufgebaut werden muss.

Function MyConnectString() As String

     MyConnectString = "Provider=Microsoft.ACE.OLEDB.12.0;" & _

         "Data Source=" & CurrentProject.Path & "󠾤_Backend.accdb;" & _

         "Mode=Share Deny None;"

End Function

Listing 1: Zusammensetzen eines ConnectionStrings für ADO

Auch das Connection-Objekt wird in einer eigenen Prozedur angelegt, die den Namen DBConnection trägt. In Listing 2 wird dabei zunächst ermittelt, ob der Parameter sConnect leer ist. In diesem Fall wird das Connection-Objekt der aktuellen Datenbank zurückgegeben. Andernfalls wird eine neue Connection angelegt, deren ConnectionString gesetzt und schließlich die Verbindung über Open geöffnet.

Function DBConnection(Optional sConnect As String) As ADODB.Connection

     If Len(sConnect) = 0 Then

         Set DBConnection = CurrentProject.Connection

     Else

         Set DBConnection = New ADODB.Connection

         DBConnection.ConnectionString = sConnect

         DBConnection.CursorLocation = adUseClient

         DBConnection.Open

     End If

End Function

Listing 2: Funktion zum Zurückgeben eines ADO-Connection-Objekts

Wichtig ist noch die Zeile davor, wo der CursorLocation der Wert adUseClient zugewiesen wird. Mit dieser Eigenschaft wird bestimmt, wie die Kommunikation mit dem OLEDB-Provider vonstattengehen soll. asUseClient sagt ihm dabei, dass die Client-Anwendung (=Access) die Steuerung übernimmt, während adUseServer hieße, dass diverse Zugriffsmethoden vom Server übernommen würden. Da eine Access-Datenbankdatei kein SQL-Server ist, macht diese Einstellung keinen Sinn. Leider ist sie aber für ein neues Connection-Objekt voreingestellt, so dass die Angabe der CursorLocation und adUseClient immer obligatorisch ist. Ohne sie treten beim anschließenden Ansprechen der Daten Fehlermeldungen auf.

Fehlt noch eine Funktion, die auf allgemeine Weise ein ADO-Recordset zur Verfügung stellt. ADORecordset in Listing 3 bewerkstelligt dies.

Function ADORecordset(sConnect As String, sSQL As String, _

         Optional bReadOnly As Boolean) As ADODB.Recordset

     Set ADORecordset = New ADODB.Recordset

     With ADORecordset

         Set .ActiveConnection = DBConnection(sConnect)

         .CursorLocation = adUseClient

         .CursorType = adOpenDynamic '= adOpenKeyset

         If Not bReadOnly Then .LockType = adLockOptimistic

         .Open sSQL

     End With

End Function

Listing 3: Zurückgeben eines ADO-Recordset-Objekts über steuernde Parameter

Als Parameter wird ihr in sConnect der ConnectionString übergeben und mit bReadOnly optional geregelt, ob das resultierende Recordset schreibgeschützt sein soll (bReadOnly = True). Die abzufragenden Daten sollen im Parameter sSQL stehen. Die Routine legt zuerst ein leeres Recordset-Objekt an. Unter ADODB ist das möglich, unter DAO nicht. Dort kann ein Recordset-Objekt immer nur über die OpenRecordset-Methoden von Database, TableDef oder QueryDef erhalten werden. Zu welcher Datenquelle eine Verbindung aufgebaut werden soll, wird über die Eigenschaft ActiveConnection angegeben

Hier muss ein zuvor bereits angelegtes Connection-Objekt vorliegen. In unserem Fall kommt dieses aus der besprochenen Funktion DBConnection. Auch das ADO-Recordset kennt die Eigenschaft CursorLocation, die auf gleiche Weise gesetzt werden kann, wie beim Connection-Objekt. Allerdings ist das nicht zwingend erforderlich, weil eine auf adUseClient gesetzte Verbindung das Recordset implizit auf diesen Modus einstellt.

Mit CursorType kann gesteuert werden, wie sich die Datenabfrage verhalten soll. Sie kennen das schon von DAO: über den optionalen Parameter von OpenRecordset können Sie etwa den Wert dbOpenDynaset setzen, der bestimmt, dass das Recordset erst beim Bewegen durch die Datensätze auf die Quelle zugreift. dbOpenStatic wäre die andere Möglichkeit, die zur sofortigen Abfrage sämtlicher Datensätze führte. Beim ADO-Recordset entspricht adOpenDynamic dem dbOpenDynaset von DAO. Beim Zuweisen dieses Werts tritt kein Fehler auf, tatsächlich aber verwendet die ACE-Provider-Engine intern den Wert adOpenKeyset. Sie kann nur diesen dynamischen Typ. Es ist also egal, welche der beiden Angaben Sie machen. Sie unterscheiden das Verhalten des Recordsets in der Folge auch nicht nennenswert.

Nun folgt das Bestücken des LockType des Recordsets mit dem Wert adLockOptimistic. Dabei handelt es sich um den Sperrmodus der Datensätze. Ohne diese Zuweisung befänden sie sich nämlich automatisch im Modus schreibgeschützt (adLockReadOnly). Daten könnten zwar gelesen, jedoch nicht bearbeitet werden. Das Ganze geschieht in Abhängigkeit vom Parameter bReadOnly. Ist ihm False übergeben, so trifft die If-Then-Bedingung zu und LockType wird entsprechend gesetzt.

Zu guter Letzt öffnet die Open-Anweisung das Recordset ähnlich, wie dies beim Connection-Objekt erfolgte. Nur ist hier zusätzlich ein SQL-String anzugeben, der die abzufragenden Daten bestimmt.

Schauen wir uns an, wie sich dieses Routinen-Set praktisch einsetzen lässt. Listing 4 zeigt ein Beispiel. Erst wird die Recordset-Variable rs über die Funktion ADORecordset gefüllt, der als Parameter der ConnectionString aus der Hilfsfunktion MyConnectString und die gewünschten Daten – hier alle Datensätze der Tabelle tblKunden – als SQL-Ausdruck übergeben wird.

Sub TestADORecordset()

     Dim rs As ADODB.Recordset

     Set rs = ADORecordset(MyConnectString, _

         "SELECT * FROM tblKunden")

     Debug.Print rs.Source

     Debug.Print rs.Fields.Count & " Felder"

     Debug.Print rs.Fields(1).Name & ": " & rs.Fields(1).Value

     rs.MoveNext

     Debug.Print rs.Fields("Kundencode").Name & ": " & rs.Fields(1).Value

     rs.Fields(1).Value = 1579518

     rs.Update

End Sub

Listing 4: Testprozedur für die ADO-Routinen im Modul mdlADODB

Es reicht hier also bereits eine Code-Zeile aus, um ein geöffnetes Recordset mit den gefragten Datensätzen zu erhalten. Der Rest der Routine wertet dann nur noch die Daten aus. Source ist eine Eigenschaft des Recordsets, die die gesetzte Datenabfrage wiedergibt. Das entspricht dem übergebenen SQL-Ausdruck. Das zeigt übrigens eine Alternative zum Übergeben des SQL-Ausdrucks an die Open-Methode des Recordsets auf: Wäre der Source-Eigenschaft schon der SQL-String verabreicht worden, so wäre die Open-Methode ohne weiteren Parameter ausgekommen!

Die weiteren Zeilen geben Daten im Direktfenster aus, wobei sich die Syntax zum Abfragen des Recordsets in keiner Weise von der unterscheidet, die auch unter DAO zum Einsatz gekommen wäre. Nur die letzten zwei Zeilen lassen den Kenner aufhorchen: da wird einem Feld ein neuer Wert spendiert und die Datensatzgruppe über die Update-Anweisung aktualisiert, ohne dass zuvor ein rs.Edit erfolgte. Tatsächlich gibt es diese Methode unter ADO auch gar nicht. Sie können einfach so einen Feldwert zuweisen, der dann über ein Update übernommen wird.

ADO-Daten im Formular

Über die besprochenen Recordset-Routinen können Sie Daten aus beliebigen Datenbanken unter VBA auslesen und bearbeiten. An dieser Stelle sollte erwähnt werden, dass es sich dabei nur um den grundlegendsten Funktionsbereich von ADODB handelt. Es hat noch einiges mehr auf Lager, das hier den Rahmen sprengen würde.

Doch können die über ADO erhaltenen Daten auch in Formularen und Berichten verwendet werden? Das lässt sich bejahen. Im Folgenden wird das Formular frmKunden der Beispieldatenbank vom gängigen Datenmodus auf ADO migriert.

Bild 2 zeigt das kleine Kundenformular, das keine Besonderheiten aufweist. Ihm liegt als Datenquelle die Tabelle tblKunden zugrunde, welche aus der Datenbank 1508_Backend.accdb in das Frontend verknüpft wurde.

Handelsübliches tabellenbasiertes Formular mit Kundenadressen

Bild 2: Handelsübliches tabellenbasiertes Formular mit Kundenadressen

Gleiches gilt für die Tabelle tblLaender, die die Basis für das im Formular eingebaute Kombinationsfeld zur Auswahl eines Landes für eine Adresse darstellt.

Hinweis: Wenn Sie die Beispieldatenbank auf Ihrem Rechner entpackt haben, dürfte der Pfad für diese verknüpften Tabellen im Frontend nicht mehr stimmen. Nehmen Sie also eine Neuverknüpfung vor, oder löschen Sie beide Tabellen und verknüpfen sie aus der Backend-Datei neu.

Die Daten im Formular lassen sich ohne Einschränkungen navigieren, neu anlegen, bearbeiten und löschen. Um es auf ADODB zu migrieren, wurde das Formular zunächst unter dem Namen frmKundenADO kopiert. Im Entwurf wurde nun aber die Datenherkunft aus ihm über das Eigenschaftenblatt entfernt. Zur Laufzeit zeigt es nun lediglich #Name in den Feldern an, weil keine Daten mehr geliefert werden.

Die Prozedur in Listing 5 zeigt, wie dem Formular nun eine ADO-Datenherkunft untergeschoben wird. Im Ereignis Beim Laden (Form_Load) wird eine modulweit gültige Variable rs vom Type ADODB.Recordset auf gleiche Weise gesetzt, wie in der Beispielroutine des Listing 4.

Private WithEvents rs As ADODB.Recordset

Private Sub Form_Load()

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!