Metadaten per Zusatztabelle verwalten

Im Laufe der Zeit können sich einen Menge Daten ansammeln, die Sie in Zusammenhang mit Kunden, Produkten oder ähnlichen Entitäten speichern wollen. Dabei möchten Sie vielleicht nicht für jede neue Eigenschaft ein neues Feld anlegen und damit den Tabellenentwurf ändern. Das gilt umso mehr, wenn eine Anwendung bereits von vielen Kunden genutzt wird. Es gibt jedoch eine Alternative: Sie können eine zusätzliche Tabelle hinzufügen, die in jedem Datensatz eine Eigenschaft mit dem jeweiligen Wert für die Entität speichert. Die Frage ist nur: Wie können wir diese Daten genauso nutzen, als wenn diese wie üblich in der gleichen Tabelle wie der Kunde oder das Produkt gespeichert werden

Nehmen wir an, Sie möchten einer Kundentabelle zwei neue Felder hinzufügen, mit denen Sie festlegen, wann der Kunde sich für den Newsletter angemeldet hat und wann er sich wieder abgemeldet hat. Sie wollen aber nicht die Kundentabelle anpassen, weil Sie das in der Vergangenheit schon öfter gemacht haben und dies immer mit viel Aufwand verbunden ist.

Stattdessen wollen Sie eine Lösung schaffen, mit der Sie langfristig zwar nach Bedarf neue Felder hinzufügen können, aber nicht jedesmal den Entwurf der Kundentabelle ändern müssen.

Lösung per Extra-Tabelle

Die Lösung ist eine zusätzliche Tabelle, welche ausschließlich Metadaten zu den Elementen der Haupttabelle speichert. Wie muss eine solche Tabelle aussehen, wenn wir dort nach Wunsch verschiedene Attribute mit ihren Werten hinzufügen wollen

Anforderungen an die Metadaten-Tabelle

Die erste Anforderung ist, dass die Daten der Metadaten-Tabelle den Einträgen der Haupttabelle zugeordnet werden können müssen. Die Metadaten-Tabelle, nennen wir Sie hier tblKundenMeta, erhält also neben dem Primärschlüsselfeld KundeMetaID ein Fremdschlüsselfeld namens KundeID. Außerdem wollen wir den Namen des Attributs und den Wert des Attributs in der Tabelle speichern. Dazu legen wir zwei weitere Felder namens Attributname und Attributwert an. Dem Feld Attributname weisen wir logischerweise den Felddatentyp Kurzer Text zu.

Aber welchen Datentyp soll das Feld Attributwert erhalten – immerhin sollen dort gegebenenfalls Daten mit verschiedenen Datentypen gespeichert werden In diesem Fall können wir nur den Datentyp Kurzer Text wählen, da alle anderen Datentypen Einschränkungen haben – zum Beispiel können Sie Zahlenfelder nicht mit Texten füllen, aber umgekehrt schon.

Jedes Attribut nur einmal pro Datensatz

Grundsätzlich sollten wir für die Kombination des Fremdschlüsselfeldes (hier KundeID) und Attributname einen eindeutigen Index festlegen, damit jedes Attribut für jeden Kunden nur einmal angelegt werden kann – so, wie es auch in einem einfachen Feld direkt in der Kundentabelle der Fall ist.

Andererseits stellt sich die Frage, ob es nicht auch Anwendungsfälle gibt, in denen mehrere Werte für ein Attribut benötigt werden, die unter dem gleichen Attributnamen abrufbar sein sollen. Dann wäre ein zusammengesetzter, eindeutiger Index nicht sinnvoll. Und was, wenn es zwar Felder gibt, die nur einmal vorkommen dürfen, andere aber mehrmals Dies müssten wir in der Prozedur prüfen und behandeln, mit der wir Daten zu der Meta-Tabelle hinzufügen.

Datenmodell von Haupt- und Metatabelle

Für unser Beispiel sieht das Datenmodell wie in Bild 1 aus. Die Beziehung zwischen den Tabellen wird über das Fremdschlüsselfeld KundeID der Tabelle tblKundenMeta hergestellt. Für diese Beziehung legen wir referentielle Integrität mit Löschweitergabe fest.

Datenmodell mit Metatabelle

Bild 1: Datenmodell mit Metatabelle

Wenn der Benutzer dann einen Datensatz der Tabelle tblKunden löscht, werden die damit verknüpften Datensätze der Tabelle tblKundenMeta automatisch mitgelöscht.

Bearbeiten der Metadaten über die Benutzeroberfläche

Die flexibelste Art, die Metadaten in einem Formular anzuzeigen, ist ein Unterformular, dass alle Datensätze der Tabelle tblKundenMeta anzeigt, die zu dem im Hauptformular gehörenden Datensatz der Tabelle tblKunden gehören. Zum Erstellen des Unterformulars gehen Sie wie folgt vor:

  • Legen Sie ein neues Formular namens sfmKundenMitMetadaten an.
  • Stellen Sie die Eigenschaft Datensatzquelle auf die Tabelle tblKundenMeta ein.
  • Ziehen Sie die Felder Attributname und Attributwert in den Detailbereich des Entwurfs.
  • Stellen Sie die Eigenschaft Standardansicht auf Datenblatt ein.
  • Schließen und speichern Sie das Unterformular.

Den Entwurf des Unterformulars mit den Eigenschaften finden Sie in Bild 2.

Entwurf des Unterformulars sfmKundenMitMetadaten

Bild 2: Entwurf des Unterformulars sfmKundenMitMetadaten

Das Hauptformular legen Sie wie folgt an:

  • Legen Sie ein neues Formular namens frmKundenMitMetadaten an.
  • Stellen Sie die Eigenschaft Datensatzquelle auf tblKunden ein.
  • Ziehen Sie alle Felder der Tabelle in den Entwurf.
  • Ziehen Sie das Formular sfmKundenMitMetadaten aus dem Navigationsbereich in den Formularentwurf.
  • Speichern und schließen Sie das Formular.

Wenn die Tabellen korrekt verknüpft sind und Sie nun das Unterformular-Steuerelement markieren, zeigen die Eigenschaften Verknüpfen von und Verknüpfen nach beide den Wert KundeID an (siehe Bild 3).

Entwurf des Hauptformulars frmKundenMitMetadaten

Bild 3: Entwurf des Hauptformulars frmKundenMitMetadaten

Attribute eingeben

Wechseln Sie in die Formularansicht des Hauptformulars, können Sie erste Daten eintragen. Beginnen Sie mit denen auf der linken Seite. Danach fügen wir rechts zuerst einen Attributnamen und dann den Wert ein. Auf diese Weise legen wir die Eigenschaften E-Mail und Newsletteranmeldung mit den gewünschten Weren (siehe Bild 4).

Eingabe von Attributen und Attributwerten

Bild 4: Eingabe von Attributen und Attributwerten

Alternative Eingabemöglichkeit

Wenn Sie nun entscheiden, dass Sie für neue Metadaten zwar keine Änderungen am Datenmodell vornehmen möchten, aber sehr wohl an der Benutzeroberfläche, wird es viel aufwendiger.

Aber warum sollte man Änderungen an der Benutzeroberfläche tolerieren, wenn man das Datenmodell nicht antasten möchte Es kann beispielsweise sein, dass das Backend nicht so einfach angepasst werden kann, weil es beispielsweise ein SQL Server-Backend ist. Beim Frontend hingegen ist das kein Problem – wenn es vernünftig programmiert ist, können Sie dieses einfach ersetzen, ohne dass der Betrieb eingeschränkt wird.

Daher wollen wir nun die beiden neuen Attribute E-Mail und Newsletteranmeldung wie die Felder der Tabelle tblKunden im Formular anzeigen. Dazu legen wir ein neues Formular namens tblKundenMitMetafeldern an.

Wenn wir eine Datensatzquelle für alle Felder erhalten wollen, benötigen wir eine entsprechend formulierte Abfrage. Diese sollte also nicht nur die Felder der Tabelle tblKunden enthalten, sondern auch noch zwei Felder namens EMail und Newsletteranmeldung.

Um es kurz zu machen: Eine solche Abfrage, die gleichzeitig auch noch aktualisierbar ist, lässt sich nicht erstellen. Aktualisierbar muss diese aber sein, weil wir auch Daten in das Formular eingeben wollen.

Also legen wir für die beiden Attribute E-Mail und Newsletteranmeldung ungebundene Textfelder an, die wir beim Anzeigen des Datensatzes mit den Werten der Tabelle tblKundenMitMetadaten füllen.

Wenn der Benutzer die Inhalte ändert, tragen wir die Änderungen per Code in diese Tabelle ein.

Die beiden Textfelder nennen wir txtEMail und txtNewsletteranmeldung. Wir fügen diese wie in Bild 5 in das Formular ein.

Ungebundene Felder für die Metadaten

Bild 5: Ungebundene Felder für die Metadaten

Meta-Attributnamen in die Marke-Eigenschaft schreiben

Für später tragen wir für die beiden Textfelder txtEMail und txtNewsletteranmeldung die jeweiligen Attributnamen in die Eigenschaft Marke ein, die Sie auf der Registerseite Andere des Eigenschaftenblatts finden.

Diese benötigen wir später für die VBA-Programmierung.

Anzeigen der Daten aus der Metadaten-Tabelle

Damit die Felder beim Anzeigen eines neuen Datensatzes gefüllt werden, hinterlegen wir eine Ereignisprozedur für das Ereignis Beim Anzeigen. Diese enthält zwei Anweisungen. Die erste verwendet die DLookup-Funktion, um den Wert des Feldes Attributwert des Datensatzes der Tabelle tblKundenMeta zu ermitteln, für den das Feld KundeID dem aktuellen Kunden und das Feld Attributname dem gesuchten Attribut entspricht (siehe Listing 1).

Private Sub Form_Current()
     Me!txtEMail = Nz(DLookup("Attributwert", "tblKundenMeta", "KundeID = " & Nz(Me!KundeID, 0) _
         & " AND Attributname = 'E-Mail'"), "")
     Me!txtNewsletteranmeldung = Nz(DLookup("Attributwert", "tblKundenMeta", "KundeID = " & Nz(Me!KundeID, 0) _
         & " AND Attributname = 'Newsletteranmeldung'"), "")
End Sub

Listing 1: Einlesen der Metadaten und Schreiben in die Textfelder

Es kann vorkommen, dass gar nicht alle gesuchten Attribute in der Tabelle tblKundenMeta vorhanden sind. Daher fassen wir das Ergebnis der DLookup-Funktion noch in die Nz-Funktion ein, die eine leere Zeichenkette zurückliefert, wenn kein passender Datensatz gefunden werden konnte.

Schließlich wird dieser Prozedur auch aufgerufen, wenn das Formular einen neuen, leeren Datensatz anzeigt. In diesem Fall ist das Feld KundeID noch leer. Also fassen wir auch dieses mit der Nz-Funktion ein und liefern den Wert 0 zurück, wenn KundeID den Wert Null hat. Die zurückgelieferten Zeichenketten landen in jedem Fall in den beiden Feldern txtEMail und txtNewsletterAngemeldet (siehe Bild 6).

Die unteren beiden Textfelder enthalten Metadaten aus der verknüpften Tabelle tblKundenMeta.

Bild 6: Die unteren beiden Textfelder enthalten Metadaten aus der verknüpften Tabelle tblKundenMeta.

Ändern der Metadaten

Nun wollen wir sicherstellen, dass Änderungen an den Daten der beiden Textfelder txtEMail und txtNewsletteranmeldung auch in der Tabelle tblKundenMeta landen.

Das soll erst dann geschehen, wenn auch die Daten aus den übrigen Textfeldern gespeichert werden, also beim Speichern des Datensatzes. Dazu eignen sich die beiden Ereignisse Vor Aktualisierung und Nach Aktualisierung des Formulars.

Wir verwenden Nach Aktualisierung, weil der Datensatz der Tabelle tblKunden zum Zeitpunkt des Auslösens des Ereignisses Vor Aktualisierung noch nicht gespeichert ist (siehe Listing 2). Dann haben wir auch noch keinen Primärschlüsselwert eines neuen Datensatzes der Tabelle tblKunden und können folglich auch keine damit verknüpften Datensätze in der Tabelle tblKundenMeta anlegen.

Private Sub Form_AfterUpdate()
     Dim db As DAO.Database
     Dim lngKundeMetaID As Long
     Set db = CurrentDb
     lngKundeMetaID = Nz(DLookup("KundeMetaID", "tblKundenMeta", "KundeID = " & Me!KundeID _
         & " AND Attributname = 'E-Mail'"), 0)
     If lngKundeMetaID = 0 Then
         db.Execute "INSERT INTO tblKundenMeta(KundeID, Attributname, Attributwert) VALUES(" & Me!KundeID _
             & ", 'E-Mail', '" & Me!txtEMail & "')", dbFailOnError
     Else
         db.Execute "UPDATE tblKundenMeta SET Attributwert = '" & Me!txtEMail & "' WHERE KundeID = " & Me!KundeID _
             & " AND Attributname = 'E-Mail'", dbFailOnError
     End If
     lngKundeMetaID = Nz(DLookup("KundeMetaID", "tblKundenMeta", "KundeID = " & Me!KundeID _
         & " AND Attributname = 'Newsletteranmeldung'"), 0)
     If lngKundeMetaID = 0 Then
         db.Execute "INSERT INTO tblKundenMeta(KundeID, Attributname, Attributwert) VALUES(" & Me!KundeID _
             & ", 'Newsletteranmeldung', '" & Me!txtNewsletteranmeldung & "')", dbFailOnError
     Else
         db.Execute "UPDATE tblKundenMeta SET Attributwert = '" & Me!txtNewsletteranmeldung & "' WHERE KundeID = " _
             & Me!KundeID & " AND Attributname = 'Newsletteranmeldung'", dbFailOnError
     End If
End Sub

Listing 2: Schreiben der Metadaten

In der Prozedur prüfen wir zunächst, ob es bereits einen Datensatz in der Tabelle tblKundenMeta gibt, der zum aktuellen Kunden gehört und das betroffene Attribut enthält.

Falls ja, landet der Primärschlüsselwert dieses Datensatzes der Tabelle tblKundenMeta in der Variablen lngKundeMetaID, anderenfalls der Wert 0.

Hat lngKundeMetaID danach den Wert 0, legen wir mit einer INSERT INTO-Abfrage einen neuen Datensatz in der Tabelle tblKundenMeta an, der das Attribut mit dem Wert aus dem Textfeld enthält. Falls nicht, aktualisieren wir den vorhandenen Datensatz für den Kunden und das Attribut mit einer UPDATE-Abfrage.

Das erledigen wir zuerst für das Feld txtEMail und dann für das Feld Newsletteranmeldung. Der Code ist recht redundant und kann ausgelagert und parametrisiert werden – dazu später mehr.

Zunächst prüfen wir, ob die Prozedur wie gewünscht funktioniert. Dazu legen wir einen neuen Datensatz an und speichern diesen dann beispielsweise durch den Wechseln zum nächsten neuen Datensatz, zum vorherigen Datensatz oder einfach durch Betätigen der Tastenkombination Strg + S.

Das funktioniert wie gewünscht – die Daten aus den beiden Textfeldern txtEMail und txtNewsletteranmeldung landen in zwei neuen Datensätzen der Tabelle tblKundenMeta.

Kein Speichern ohne “dreckigen” Datensatz

Nun probierern wir uns an einem vorhandenen Datensatz und ändern ein oder mehrere der gebundenen Felder sowie die beiden Textfelder txtEMail und txtNewsletteranmeldung. Auch das gelingt: Die geänderten Daten der beiden ungebundenen Textfelder landen in den entsprechenden Datensätzen der Tabelle tblKundenMeta.

Wenn wir allerdings nur die Daten von txtEMail und/oder txtNewsletteranmeldung ändern, werden die Änderungen nicht übernommen. Der Grund ist, dass wir den Datensatz nur durch das Ändern der ungebundenen Textfelder nicht in den bearbeiteten Zustand versetzen und somit auch das Ereignis Nach Aktualisierung beim Verlassen des Datensatzes nicht ausgelöst wird.

Wir müssen also irgendwie dafür sorgen, dass die Eigenschaft Dirty des Formulars den Wert True erhält, dass der Datensatz also als “dreckig” markiert wird. Das wollen wir erledigen, sobald der Benutzer eine Änderung in einem der beiden Textfelder txtEMail oder txtNewsletteranmeldung vorgenommen hat. Wir dachten, das wäre mit den folgenden beiden Prozedur schnell erledigt:

Private Sub txtEMail_Change()
     Me.Dirty = True
End Sub
Private Sub txtNewsletteranmeldung_Change()
     Me.Dirty = True
End Sub

Allerdings lösen wir damit nur den Fehler aus Bild 7 aus. Wir müssen also den Fokus auf eines der gebundenen Steuerelemente setzen, um den Datensatz als bearbeitet zu markieren. Wie erledigen wir das, ohne das der Benutzer es mitbekommt

Die Dirty-Eigenschaft lässt sich nur setzen, wenn ein gebundenes Feld den Fokus hat.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar