Gelegentlich ist es sinnvoll, Daten eines Datensatzes aus einer Tabelle in einem Objekt auf Basis einer Klasse abzulegen – vor allem dann, wenn Sie nur auf einen kompakten Satz von Daten zugreifen möchten und diese nicht ändern wollen. Leider ist der nötige Code je nach Anzahl der Felder der Datenherkunft und somit der Eigenschaften recht umfangreich und die Erstellung eine mühselige Arbeit. Mit dem in diesem Beitrag vorgestellten Add-In soll dies ganz schnell von der Hand gehen.
Die Beispieldatenbank ist ein fertiges Add-In. Zur Installation starten Sie den Add-In-Manager und wählen die Datei Klassengenerator.mda aus. Anschließend finden Sie im Add-In-Menü den Eintrag aiuKlassengenerator. Es erscheint der Dialog aus Bild 1. Hier lesen Sie entweder die Daten aller Tabellen neu ein (was beim ersten Öffnen in einer Datenbank automatisch geschieht) und wählen per Kombinationsfeld die Tabelle oder Abfrage aus, zu der Sie eine Klasse erstellen möchten. Das Unterformular zeigt dann alle Felder dieser Datenherkunft. Legen Sie dort fest, welche Felder als Eigenschaften der Klasse berücksichtigt werden sollen, und passen Sie gegebenenfalls von den Feldnamen abweichende Eigenschaftsnamen an. Klicken Sie dann auf Erzeugen, um die komplette Klasse auf Basis der Datenherkunft in Sekundenbruchteilen zu erstellen. Nach Wunsch fügen Sie dem Code auch gleich eine Prozedur hinzu, die eine Routine zum Erstellen und Füllen einer Instanz dieser Klasse erstellt. Die erzeugten Quelltexte müssen Sie nur noch in die Zielmodule kopieren.
Bild 1: Der Klassengenerator in Aktion
Datenmodell
Das Add-In soll die Auswahl einer Tabelle oder Abfrage ermöglichen, für deren Felder eine Klasse angelegt werden soll. Für diese Tabelle soll das Add-In alle Felder anzeigen. Der Benutzer legt fest, welche Felder berücksichtigt werden sollen und wie die entsprechenden Eigenschaften in der Klasse heißen sollen.
Das Add-In soll die Namen der Tabellen und Abfragen in der Datenbank, von der aus das Add-In gestartet wurde, in einem Kombinationsfeld zur Auswahl anbieten. Der direkte Zugriff von einem Kombinationsfeld innerhalb eines Add-Ins auf die Daten der Host-Datenbank ist nicht möglich, also schreiben wir die notwendigen Daten zuvor in eine Tabelle innerhalb der Add-In-Datenbank.
Diese Tabelle enthält die Felder TabelleAbfrageID (Primärschlüsselfeld, Autowert), TabelleAbfrage, Datenbank, Klassenname, Primaerschluesselfeld und Objekttyp. TabelleAbfrage nimmt die Namen der in der Datenbank enthaltenen Tabellen und Abfragen auf (s. Bild 2). Datenbank speichert den Namen der Zieldatenbank, damit einmal definierte Schemata zur Erzeugung von Klassen erneut verwendet werden können. Das Feld Klassenname nimmt den Namen auf, unter dem die Klasse für diese Tabelle oder Abfrage erzeugt werden soll – also beispielsweise clsPerson. Primaerschluesselfeld enthält den Namen des entsprechenden Feldes der Tabelle. Bei Tabellen wird dieser später automatisch ermittelt, bei Abfragen muss er manuell eingetragen werden. Objekttyp nimmt den Zahlenwert des Feldes Type der Tabelle MSysObjects auf – dazu später mehr.
Bild 2: Diese Tabelle speichert die Namen der Tabellen und Abfragen der Zieldatenbank.
Die zweite Tabelle heißt tblFelder (s. Bild 3). Sie enthält neben dem Primärschlüsselfeld FeldID folgende Felder:
Bild 4: Beziehung der beiden Tabellen des Add-Ins
- Feldname: Name des Feldes in der Tabelle
- Eigenschaftsname: Name der Eigenschaft für das Feld in der Klasse
- Erzeugen: Gibt an, ob das Feld beim Erzeugen des Klassencodes berücksichtigt werden soll
- TabelleAbfrageID: Fremdschlüsselfeld zur Tabelle tblTabellenAbfragen
- Datentyp: Nummer des Datentyps des Feldes
Die Beziehung zwischen den beiden Tabellen ist mit referentieller Integrität definiert und Löschweitergabe. Wenn also ein Datensatz aus der Tabelle tblTabellenAbfragen gelöscht wird, entfernt Access auch die mit dem entsprechenden Datensatz verknüpften Datensätze der Tabelle tblFelder (s. Bild 4).
Bild 3: Die Tabelle tblFelder speichert die Felder und ihre Einstellungen.
Formular des Add-Ins
Damit der Benutzer die Tabelle/Abfrage für die Generierung der Klasse auswählen und die Felder festlegen kann, die das Add-In dabei berücksichtigen soll, zeigt das Add-In eine entsprechende Benutzeroberfläche in Form eines Formulars an. Dieses heißt frmKlassengenerator und bietet zunächst ein Kombinationsfeld zur Auswahl der zu verwendenden Tabelle oder Abfrage an. Außerdem soll das Hauptformular jeweils einen Datensatz der Tabelle tblTabelleAbfrage anzeigen – und zwar denjenigen, der vom Benutzer mit dem im Anschluss beschriebenen Kombinationsfeld ausgewählt wurde. Dazu stellen Sie für das Formular die Eigenschaft Datenherkunft auf die Tabelle tblTabelleAbfrage ein. Ziehen Sie außerdem die beiden Felder Datenbank und Klassenname aus der Feldliste in den Entwurf des Formulars. Lassen Sie darüber noch ein wenig Platz für ein Kombinationsfeld zur Auswahl der Tabelle/Abfrage (s. Bild 5). Wir schauen uns zunächst das Kombinationsfeld an und den Mechanismus, der die Datensatzherkunft des Kombinationsfeldes füllt und filtert.
Bild 6: Kombinationsfeld zur Auswahl der Tabellen und Abfragen der Zieldatenbank
Das Kombinationsfeld soll alle Tabellen der aktuellen Datenbank wie in Bild 6 anzeigen. Damit dieses überhaupt Daten anzeigt, weisen Sie diesem zunächst eine entsprechende Datenherkunft zu – und zwar die Tabelle tblTabellenAbfragen. Damit das Kombinationsfeld das Primärschlüsselfeld der Tabelle ausblendet und nur die Namen der Tabellen und Abfragen anzeigt, stellen Sie die beiden Eigenschaften Spaltenanzahl und Spaltenbreiten auf die Werte 2 und 0cm ein. Nachdem Sie in der Formularansicht geprüft haben, dass das Kombinationsfeld die Werte wie gewünscht anzeigt, können Sie die Eigenschaft Datensatzherkunft wieder leeren – wir füllen diese später zur Laufzeit. Immerhin soll das Kombinationsfeld ja nur die Tabellen und Abfragen der aktuellen Datenbank anzeigen. Die Tabelle tblTabellenAbfragen soll jedoch nach der Verwendung des Add-Ins in verschiedenen Anwendungen auch die entsprechenden Daten zu den jeweiligen Anwendungen enthalten.
Bild 5: Entwurfsansicht des aktuellen Stands des Formulars frmKlassengenerator
Deshalb löst das Formular beim Öffnen das Ereignis Beim Laden aus und somit die Ereignisprozedur aus Listing 1. Diese Prozedur speichert zunächst den Namen der Zieldatenbank in der Variablen strDatenbank (beim Erstellen und Testen des Add-Ins öffnen Sie dieses direkt und nicht von einer anderen Anwendung aus, daher enthält strDatenbank hier den Namen der Add-In-Datenbank selbst).
Listing 1: Füllen des Kombinationsfeldes beim Öffnen des Formulars
Private Sub Form_Load() Dim strDatenbank As String strDatenbank = CurrentProject.Name If IsNull(FLookup("TabelleAbfrageID", "tblTabellenAbfragen", "Datenbank = ''" & strDatenbank _ & "''")) Then TabellenAbfragenEinlesen End If Me!cboTabelleAbfrage.RowSource = "SELECT * FROM tblTabellenAbfragen WHERE Datenbank = ''" _ & strDatenbank & "''" End Sub
Ein Aufruf der Funktion FLookup mit den entsprechenden Parametern prüft, ob es in der Tabelle tblTabellenAbfragen bereits mindestens einen Datensatz gibt, dessen Feld Datenbank den Namen der aktuellen Datenbank enthält. Falls ja, wird die Prozedur TabellenAbfragenEinlesen aufgerufen, welche die Tabelle tblTabellenAbfragen mit den Namen der Tabellen und Abfragen der Zieltabelle füllt. Anschließend stellt die Prozedur die Datensatzherkunft des Kombinationsfeldes cboTabelleAbfrage auf eine SQL-Abfrage ein, die alle Datensätze der Tabelle tblTabellenAbfragen liefert, deren Feld Datenbank den Namen der aktuellen Zieldatenbank enthält (beziehungsweise im Standalone-Modus den Namen der Add-In-Datenbank).
Warum nutzen wir hier eine Funktion namens FLookup – heißt diese nicht üblicherweise DLookup Das ist richtig. FLookup ist ein Nachbau der DLookup-Funktion, die allerdings auf eine Datensatzgruppe zugreift, die sich in der mit CodeDB referenzierten Datenbank befindet. DLookup greift, auch von einem Add-In aus, immer auf die Tabellen oder Abfragen der Datenbank zu, die das Add-In geöffnet hat.
Tabellen und Abfragen einlesen
Nun sind beim ersten Start des Formulars noch keine Tabellen oder Abfragen in der Tabelle tblTabellenAbfragen gespeichert. Also ruft die Prozedur Form_Load auf jeden Fall die Prozedur TabellenAbfragenEinlesen auf (s. Listing 2). Diese Prozedur deklariert gleich zwei Database-Objekte. Das erste heißt dbHost und wird mit der Funktion CurrentDb gefüllt. Dies liefert einen Verweis auf die aktuell in Access geöffnete Datenbank. Wenn Sie das Add-In von einer anderen Datenbank aus geöffnet haben, handelt es sich um die öffnende Datenbank, wenn Sie das Add-In direkt geöffnet haben, wird hier die Add-In-Datenbank selbst referenziert. Die zweite Variable namens dbAddIn erhält ihren Verweis auf das Database-Objekt über die Funktion CodeDB. Dieses liefert immer einen Verweis auf die Datenbank, in welcher sich der ausführende Code befindet.
Listing 2: Füllen der Tabelle tblTabellenAbfragen
Private Sub TabellenAbfragenEinlesen() Dim dbHost As DAO.Database Dim dbAddIn As DAO.Database Dim rst As DAO.Recordset Dim strKlassenname As String Set dbHost = CurrentDb Set dbAddIn = CodeDb Set rst = dbHost.OpenRecordset("SELECT Name, Type FROM MSysObjects WHERE Type IN (1,4,5,6) " _ & "AND Name NOT LIKE ''~*'' ORDER BY Name", dbOpenDynaset) Do While Not rst.EOF strKlassenname = KlassennameErmitteln(rst!Name) dbAddIn.Execute "INSERT INTO tblTabellenAbfragen(TabelleAbfrageName, Datenbank, Klassenname) " _ & "VALUES(''" & rst!Name & "'', ''" & CurrentProject.Name & "'', ''" & strKlassenname & "'')", _ dbFailOnError rst.MoveNext Loop Me.Requery Me!cboTabelleAbfrage.Requery Set dbHost = Nothing Set dbAddIn = Nothing End Sub
Mit diesen beiden Verweisen ausgestattet öffnet die Prozedur eine Datensatzgruppe auf Basis der Tabelle MSysObjects der Zieldatenbank. Die Datensatzgruppe liefert das Feld Name aller Datensätze zurück, deren Feld Type den Wert 1, 4, 5 oder 6 enthält und deren Name nicht mit ~ beginnt (dies sind Abfragen, die als Wert der Eigenschaften Datenherkunft beziehungsweise Datensatzherkunft gespeichert, aber nicht im Datenbankfenster oder Navigationsbereich angezeigt werden). Der Wert 1 für das Feld Type gibt an, dass es sich um eine eingebaute Tabelle handelt. 4 kennzeichnet eine per ODBC verknüpfte Tabelle, 5 eine Abfrage und 6 eine per Jet verknüpfte Tabelle – also beispielsweise eine externe Excel- oder Access-Tabelle oder Textdatei.
In einer Do While-Schleife über diese Datensätze trägt die Prozedur für jede Tabelle oder Abfrage einen Datensatz in die Tabelle tblTabellenAbfragen ein. Dazu ermittelt diese mit der Funktion KlassennameErmitteln den Namen der zu erzeugenden Klasse – mehr dazu weiter unten. Eine INSERT INTO-Anweisung füllt die Felder TabelleAbfrageName, Datenbank und Klassenname mit den ermittelten Werten. Anschließend aktualisiert die Prozedur die Datenherkunft des Formulars und des Kombinationsfeldes.
Felder der gewählten Datenherkunft anzeigen
Ein Unterformular namens sfmKlassengenerator liefert die Daten zu den in der Tabelle/Abfrage enthaltenen Feldern. Nach der Anzeige der Tabellen und Abfragen der aktuellen Datenbank und der Auswahl eines der Objekte durch den Benutzer soll das Unterformular alle Felder dieser Datenherkunft in der Datenblattansicht anzeigen. Dazu erstellen Sie zunächst ein Unterformular namens sfmKlassengenerator. Weisen Sie seiner Eigenschaft Datenherkunft die Tabelle tblFelder zu. Ziehen Sie die drei Felder Feldname, Eigenschaftsname und Erzeugen in den Detailbereich des Formularentwurfs und stellen Sie die Eigenschaft Standardansicht des Formulars auf Datenblatt ein (s. Bild 7).
Bild 7: Unterformular zur Anzeige der Felder der Tabelle oder Abfrage
Speichern Sie das Formular, öffnen Sie das Hauptformular frmKlassengenerator in der Entwurfsansicht und ziehen Sie das Unterformular sfmKlassengenerator aus dem Datenbankfenster beziehungsweise dem Navigationsbereich in den Detailbereich des Hauptformulars. Das Ergebnis sieht nun etwa wie in Bild 8 aus. Nun sorgen Sie dafür, dass nach der Auswahl einer Tabelle oder Abfrage durch den Benutzer die entsprechenden Felder im Unterformular angezeigt werden. Dazu müssen diese zunächst einmal in die Tabelle tblFelder eingetragen werden – dies ist bislang noch nicht geschehen.
Bild 8: Hauptformular mit integriertem Unterformular in der Entwurfsansicht
Dazu fügen Sie für das Kombinationsfeld cboTabelleAbfrage eine neue Ereignisprozedur für das Ereignis Nach Aktualisierung hinzu (s. Listing 3). Diese Prozedur verwendet wiederum zwei Objektvariablen des Typs Database – eines für die Add-In-Datenbank und eines für die aufrufende Datenbank. Die Prozedur prüft zunächst, ob der Benutzer überhaupt einen Eintrag im Kombinationsfeld cboTabelleAbfrage ausgewählt hat.
Listing 3: Füllen der Tabelle tblFelder
Private Sub cboTabelleAbfrage_AfterUpdate() Dim dbHost As DAO.Database Dim dbAddIn As DAO.Database Dim rst As DAO.Recordset Dim fld As DAO.Field Dim bolEinlesen As Boolean If Not IsNull(Me!cboTabelleAbfrage) Then bolEinlesen = True If Not IsNull(FLookup("FeldID", "tblFelder", "TabelleAbfrageID = " & Me!cboTabelleAbfrage)) Then bolEinlesen = _ MsgBox("Felder neu einlesen und vorhandene Daten überschreiben", vbYesNo) = vbYes End If If bolEinlesen Then Set dbHost = CurrentDb Set dbAddIn = CodeDb dbAddIn.Execute "DELETE FROM tblFelder WHERE TabelleAbfrageID = " _ & Me!cboTabelleAbfrage, dbFailOnError Set rst = dbHost.OpenRecordset("SELECT * FROM " & Me!cboTabelleAbfrage.Column(1) _ & " WHERE 1=2", dbOpenDynaset) For Each fld In rst.Fields dbAddIn.Execute "INSERT INTO tblFelder(Feldname, Eigenschaftsname, " _ "TabelleAbfrageID, Erzeugen, Datentyp) VALUES(''" & fld.Name & "'', ''" _ & fld.Name & "'', " & Me!cboTabelleAbfrage & ", -1, " & fld.Type & ")", _ dbFailOnError Next fld End If Me!sfmKlassengenerator.Form.Requery Me.Filter = "TabelleAbfrageID = " & Me!cboTabelleAbfrage Me.FilterOn = True End If End Sub