Direktzugriff auf Tabellen und Felder

Die IntelliSense-Erweiterung für Tabellen und Felder hat mir soviel Spaß gemacht, dass ich gleich noch ein paar Funktionen nachreiche. Und Ihr Feedback hat natürlich auch dazu beigetragen! Die Neuerungen beziehen sich nun weniger auf IntelliSense selbst, sondern auf die Eigenschaften der Tabellenklassen für den Datenzugriff. Die heutige Version enthält eine Funktion zum Zählen der enthaltenen Datensätze, zum Filtern und zum einfachen Eingeben eines Suchkriteriums. Und das alles kann nach wie vor auch einfach vom Direktfenster aus geschehen!

Zählen von Datensätzen

Im ersten Beispiel möchte ich eine Möglichkeit schaffen, auf die Schnelle die Anzahl der Datensätze eines Recordsets zu ermitteln. Dazu habe ich der Klasse die Eigenschaft Count hinzugefügt (wieso eigentlich RecordcountCount reicht doch völlig aus!). Im Direktfenster ermitteln Sie beispielsweise wie folgt die Anzahl der Datensätze:

Filtern von Datensätzen

Ebenfalls interessant ist natürlich das Filtern von Datensätzen, um diese später zu durchlaufen. Das Filtern geschieht über die Eigenschaft Filter und der Übergabe des Filterkriteriums als Parameter. Dies wirkt sich auf das aktuell in tblArtikel gespeicherte Recordset aus, sodass Sie gleich danach die Anzahl der gefilterten Datensätze ermitteln können:

Nach dem Filtern können Sie entsprechend noch die gefilterten Datensätze per Do While-Schleife durchlaufen und beispielsweise ausgeben:

Public Sub BeispielFilter()
tblArtikel.Filter "Artikelname LIKE 'C*'"
Do While Not tblArtikel.EOF
Debug.Print tblArtikel.Artikelname
tblArtikel.MoveNext
Loop
End Sub

Filter aufheben

Zum Aufheben des Filters verwenden Sie einfach die Methode ClearFilter:

Schnelle Suche

Richtig cool ist das folgende Feature: Dort können Sie für die Tabelle gleich einen Suchausdruck eingeben und auf die Daten des gefundenen Datensatzes zugreifen. Das sieht wie folgt aus:

Dies lässt sich sogar verschachteln. Wenn Sie also etwa den Namen der Kategorie ermitteln wollen, die für den Artikel mit dem Wert 1 im Feld ArtikelID gespeichert ist, verwenden Sie den folgenden Ausdruck:

? tblKategorien("KategorieID = " & tblArtikel("ArtikelID = 1").KategorieID).Kategoriename
Getränke

Code zum Erstellen der Tabellenklassen

Sie benötigen für jede Tabelle eine eigene Klasse, um die oben genannten Funktionen nutzen zu können. Diese Klasse erstellen Sie allerdings mit einem einzigen Aufruf der folgenden Prozedur. Diese legt für jede Tabelle eine Klasse an. Gegebenenfalls kann es zu einem Fehler beim Anlegen der Klassen kommen, vor allem, wenn diese bereits vorhanden waren und überschrieben werden sollen. In diesem Fall löschen Sie alle bereits vorhandenen Tabellenklassen von Hand und schließen alle Codefenster außer dem mit der Prozedur TabelleZuKlasse:

Public Sub TabelleZuKlasse()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim objCodeModule As VBIDE.CodeModule
Dim objVBProject As VBIDE.VBProject
Dim objVBComponent As VBIDE.VBComponent
Dim strKlasse As String
Dim strCode As String
Set objVBProject = VBE.ActiveVBProject
Set db = CurrentDb
For Each tdf In db.TableDefs
If tdf.Name Like "tbl*" Then
On Error Resume Next
objVBProject.VBComponents.Remove objVBProject.VBComponents(tdf.Name)
On Error GoTo 0
End If
Next tdf
For Each tdf In db.TableDefs
If tdf.Name Like "tbl*" Then
Set objVBComponent = objVBProject.VBComponents.Add(vbext_ct_ClassModule)
objVBComponent.Name = tdf.Name
strCode = ""
strCode = strCode & "Dim db As DAO.Database" & vbCrLf
strCode = strCode & "Dim rst As DAO.Recordset" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Private Sub Class_Initialize()" & vbCrLf
strCode = strCode & " Set db = CurrentDb" & vbCrLf
strCode = strCode & " Set rst = db.OpenRecordset(""SELECT * FROM " & tdf.Name & """, dbOpenDynaset)" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub FindFirst(strFilter As String)" & vbCrLf
strCode = strCode & " rst.FindFirst strFilter" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Function FindFirstX(strFilter As String) As " & tdf.Name & vbCrLf
strCode = strCode & " Dim objX As " & tdf.Name & vbCrLf
strCode = strCode & " Set objX = New " & tdf.Name & vbCrLf
strCode = strCode & " objX.FindFirst strFilter" & vbCrLf
strCode = strCode & " Set FindFirstX = objX" & vbCrLf
strCode = strCode & "End Function" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub MoveNext()" & vbCrLf
strCode = strCode & " rst.MoveNext" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub MovePrevious()" & vbCrLf
strCode = strCode & " rst.MovePrevious" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub MoveFirst()" & vbCrLf
strCode = strCode & " rst.MoveFirst" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub MoveLast()" & vbCrLf
strCode = strCode & " rst.MoveLast" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Function Count() As Long" & vbCrLf
strCode = strCode & " Dim var As Variant" & vbCrLf
strCode = strCode & " var = rst.Bookmark" & vbCrLf
strCode = strCode & " rst.MoveLast" & vbCrLf
strCode = strCode & " Count = rst.RecordCount" & vbCrLf
strCode = strCode & " rst.MoveFirst" & vbCrLf
strCode = strCode & " rst.Bookmark = var" & vbCrLf
strCode = strCode & "End Function" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Function Filter(strFilter As String)" & vbCrLf
strCode = strCode & " Set rst = db.OpenRecordset(""SELECT * FROM " & tdf.Name & " WHERE "" & strFilter, dbOpenDynaset)" & vbCrLf
strCode = strCode & "End Function" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Sub ClearFilter()" & vbCrLf
strCode = strCode & " Set rst = db.OpenRecordset(""SELECT * FROM " & tdf.Name & """, dbOpenDynaset)" & vbCrLf
strCode = strCode & "End Sub" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Function EOF() As Boolean" & vbCrLf
strCode = strCode & " EOF = rst.EOF" & vbCrLf
strCode = strCode & "End Function" & vbCrLf
strCode = strCode & vbCrLf
strCode = strCode & "Public Function BOF() As Boolean" & vbCrLf
strCode = strCode & " BOF = rst.BOF" & vbCrLf
strCode = strCode & "End Function" & vbCrLf
strCode = strCode & vbCrLf
For Each fld In tdf.Fields
strCode = strCode & "Public Property Get " & fld.Name & "()" & vbCrLf
strCode = strCode & " " & fld.Name & " = rst!" & fld.Name & vbCrLf
strCode = strCode & "End Property" & vbCrLf
Next fld
objVBComponent.CodeModule.AddFromString strCode
objVBComponent.Export CurrentProject.Path & "" & objVBComponent.Name
objVBProject.VBComponents.Remove objVBComponent
Open CurrentProject.Path & "" & tdf.Name For Input As #1
strKlasse = Input$(LOF(1), #1)
Close #1
strKlasse = Replace(strKlasse, "Attribute VB_PredeclaredId = False", "Attribute VB_PredeclaredId = True")
strKlasse = Replace(strKlasse, "Public Function FindFirstX(strFilter As String) As " & tdf.Name, "Public Function FindFirstX(strFilter As String) As " & tdf.Name & vbCrLf & "Attribute DefaultMethod.VB_UserMemId = 0")
Open CurrentProject.Path & "" & tdf.Name For Output As #1
Print #1, strKlasse
Close #1
objVBProject.VBComponents.Import CurrentProject.Path & "" & tdf.Name
DoCmd.Save acModule, tdf.Name
End If
Next tdf
Set db = Nothing
End Sub

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zahlenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

  • Verblüffende und geniale Idee - mit kleinem Haken?

    Grundsätzlich finde ich die Idee mit dem Tabellen-Klassengenerator und der Auto-Instanziierung der Zugriffsobjekte mit dem VB_PredeclaredId-Attribut (den Trick kannte ich noch nicht) echt genial und vor allem ungmein nützlich in der Praxis.

    Mein einziges Bedenken resultiert aus der Tatsache, dass die Tabellenobjekte grundsätzlich ein DAO-Recordset-Objekt vom Typ Dynaset offen halten - für jede Tabelle eines, für die gesamte Zeit, in der dieDatenbank geöffnet ist. In früheren Access-Versionen habe ich die Erfahrung machen müssen, dass die DAO-Resourcen generell beschränkt sind, und dass nicht ordnungsgemäß geschlossene und de-referenzierte DAO-Objekte (Database, Recordset) teilweise dazu geführt haben, dass sich die Datenbanken nicht regulär schließen ließen (Hänger beim Schließen).

    Ob ein entsprechendes "Aufräumen" im Terminate-Ereignis der Klassen hier hilfreich sein könnte? Oder sind meine Bedenken in den neueren Access-Versionen eher gegenstandlos geworden?

  • Der Haken ...

    Um genau zu sein, würde ich die Klassen vorerst nicht im Produktivbetrieb einsetzen, sondern vorerst nur beim Entwickeln - beispielsweise, um mal schnell Daten zu prüfen.
    Ob und wie man das Schließen der Recordsets regelt, müsste noch geprüft werden.

  • Datentypen

    Hallo,

    Könnte man nicht in den Tabellen- / Spaltendefinitionen die Datentypen auslesen und diese den Gettern als Rückgabewert mitgeben?

    Viele Grüße

  • Genial und ausbaufähig

    Die Idee an sich ist genial und der Code dazu fast schon verblüffend einfach.
    Dem versierten Entwickler fällt natürlich zuerst das fehlende Exception-Handling auf, aber bei 'nem 'proof of concept' ist das ja erstmal zu verschmerzen.
    Das Ganze funktioniert auch bei verknüpften Oracle-Tabellen erstaunlich gut und schnell.
    Leider habe ich an der Ecke mit einigen 'Altlasten' zu kämpfen, die sich z.B. durch Umlaute in Feldnamen bemerkbar machen. Das führt natürlich unweigerlich zu Problemen beim Generieren der Klassen.
    Auch dass durch das 'Set db = CurrentDb' immer wieder eine neue Instanz der aktuellen DB geholt wird ist eher suboptimal. 'Set db = DbEngine(0)(0)' scheint hier eine gangbare Alternative zu sein.
    Das Ganze kann man aber - wie bereits bemerkt - sicher ausbauen.
    Ich könnte mir auch gut vorstellen, das als kleines 'Entwickler-Gimmick' mit in OASIS-SVN aufzunehmen.

Passende Artikel
BUNDLE
Access im Unternehmen Access im Unternehmen
124,00 € * 159,00 € *
Aktuell im Blog
Onlinebanking mit Access

Es ist geschafft: Endlich ist das Buch Onlinebanking mit Access fertiggeschrieben. Das war... [mehr]

Direktzugriff auf Tabellen und Felder

Die IntelliSense-Erweiterung für Tabellen und Felder hat mir soviel Spaß gemacht, dass ich gleich... [mehr]

IntelliSense für Tabellen und Felder

Wenn Sie mit dem VBA-Editor arbeiten und dort gelegentlich SQL-Anweisungen eingeben, müssen Sie... [mehr]

Download Access und SQL Server

Erfahren Sie, welche Schritte zum Download des aktuellen Stands des Buchs "Access und SQL Server"... [mehr]

Bilder in Access 2013

Wer die Bibliothek mdlOGL0710 von Sascha Trowitzsch oder ein ähnliches Modul aus meinen... [mehr]

Dynamische Ribbons

Immer wieder fragen Leser, wie man Ribbon-Elemente wie etwa Schaltflächen in Abhängigkeit... [mehr]

Die Blogmaschine

Einen kleinen Blog zusätzlich zum Shop zu betreiben ist eine tolle Sache. Hier lassen sich... [mehr]

Wegwerfadressen für die Newsletteranmeldung

Die Verwendung von Wegwerf-Adressen für die Nutzung aller möglichen Online-Dienste nimmt... [mehr]

Access und Facebook

Facebook und Access - das ist eine der wenigen Kombinationen, die ich noch nicht in die Mangel... [mehr]

Access und SQL Server - das Projekt

Mein neues Buch Access und SQL Server (gemeinsam mit Bernd Jungbluth) geht in die Endphase. Wer... [mehr]