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 Zeichenfolge 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
Access im Unternehmen Access im Unternehmen
124,00 € * 159,00 € *
Neues aus unseren Magazinen
Listenfeld: Reihenfolge mehrerer Einträge...

Wir haben bereits in mehreren Beiträgen beschrieben, wie Sie die individuelle Reihenfolge von Elementen einer Tabelle über den Inhalt eines Feldes etwa namens »ReihenfolgeID« einstellen können –... [mehr]

Diagramme mit gefilterten Daten

In Ausgabe 2/2019 haben wir in zwei Artikeln die modernen Diagramme von Access vorgestellt. Im vorliegenen Beitrag zeigen wir Ihnen, wie Sie diese abhängig von den in einem Formular angezeigten... [mehr]

Benutzerverwaltung mit verschlüsselten...

Wenn Sie in einer Access-Anwendung Benutzer verwalten wollen, die sich per Benutzername und Kennwort an die Anwendung anmelden, sollten Sie sehr sensibel mit den in der Anwendung gespeicherten... [mehr]

HTML-Tabellen mit fester Kopfzeile

In den vorherigen Ausgaben von Access im Unternehmen und in der aktuellen Ausgabe arbeiten wir in einigen Beiträgen mit dem Webbrowser-Steuerelement und stellen Daten, die wir mit den Bordmitteln... [mehr]

Flexible HTML-Tabellen mit fester Kopfzeile

Im Beitrag »HTML-Tabellen mit fester Kopfzeile« haben wir gezeigt, wie Sie Daten aus einer bestimmten Abfrage in einem Webbrowser-Steuerelement so anzeigen, dass die Spaltenköpfe oben fixiert... [mehr]

Berechtigungen per HTML verwalten

Im Beitrag »Benutzerverwaltung mit verschlüsselten Kennwörtern« stellen wir eine Lösung vor, in der wir die Berechtigungen von Benutzergruppen an Datenbankobjekten definieren. Dort benötigen wir... [mehr]

Benutzer und Berechtigungen ermitteln

In den Beiträgen »Benutzerverwaltung mit verschlüsselten Kennwörtern« und »Berechtigungen per HTML verwalten« haben wir die Voraussetzungen für eine Benutzerverwaltung geschaffen. Im vorliegenden... [mehr]

Zugriffsrechte mit Datenmakros

Es gibt verschiedene Möglichkeiten, auf Basis des aktuell angemeldeten Benutzers sicherzustellen, dass dieser nur die für ihn vorgesehenen Aktionen mit Daten durchführen darf – beispielsweise durch... [mehr]

Kennwörter generieren

Für den einen oder anderen Zweck möchten Sie vielleicht Kennwörter generieren oder in einer Benutzeroberfläche die Möglichkeit zum Generieren von Kennwörtern anbieten. Wenn Sie etwa Benutzer zu... [mehr]

Neuer Datensatz von Frontend zu Backend

Für manche Themen gibt es keine kurze, prägnante Überschrift. In diesem Fall wollen wir zeigen, wie Sie einen neuen Datensatz anlegen, der in einer temporären Tabelle im Frontend gespeichert wird,... [mehr]