Datenbanken mit ADOX
von Robert Koch
Übersicht
Hier zeige ich Ihnen, wie man mit ADO Ext Datenbanken erstellt.
Mit freundlichen Grüßen
Robert Koch webmaster@web.de
Inhalt
Erste Schritte
Bevor man munter draufloslegt und es kaum erwarten kann, eine Datenbank zu erstellen, solltet ihr euch vorher im Klaren sein, wie eure Datenbank aussehen soll. Erstellt mit Access eine Datenbank, erstellt eure Tabellen, Spalten und -eigenschaften und die Beziehungen zwischen den Tabellen.
Als ersten Schritt müsst ihr im Menü Projekt --> Verweise anklicken. Dort sucht ihr folgenden Verweis:
Microsoft ADO Ext. 2.x for DDL and Security
Wobei x die Versionsnr. ist (ist entweder 2.5 oder 2.7).
Damit wäre der erste Schritt getan, ihr habt soeben erfolgreich die Klasse in Euer Projekt mit aufgenommen.
Das Modul
Es empfiehlt sich für die Datenbankerstellung den Programmcode in ein Modul auszulagern. Der wichtigste von allen Gründen, was für eine Auslagerung des Codes in ein Modul spricht, ist die Wiederverwendbarkeit.Eine Datenbank erstellt man nur einmal für eine Anwendung. Jedoch kann man das Modul in andere Projekten laden und spart dadurch eine enorme Menge Tipparbeit, da man nur einzelne Teile anpassen muss und nicht wieder bei Null anfängt. Ein weiterer wichtiger Grund ist, dass der normale Programmcode von diesem Code getrennt ist und man leichter die Übersicht behält.
Um ein neues Modul zu erstellen, klickt ihr auf Projekt --> Modul hinzufügen.
Im Eigenschaftsfenster seht ihr, das das Modul als Name standardmäßig Module1 benannt wird. Ich benenne es um in modDB und werde diesen Namen im weiteren Verlauf verwenden. Wie man das Modul dann aufruft, erkläre ich etwas später.
Option Explicit Dim db As New ADOX.Catalog Dim table(x) As New ADOX.table Dim column(x) As New ADOX.column Dim idx(x) As New ADOX.Index Dim relation(x) As New ADOX.Key Dim dbhandle As String
Dies sind die ersten Befehle, die in dem Modul stehen müssen! x steht jeweils für eine Zahl, die ihr einsetzen müsst. Diese Befehle erstellen neue Instanzen der jeweiligen Klassen und weisen diese den Variablen zu.
Dim db As New ADOX.Catalog
Dieser Befehl erstellt ein Objekt, das den Datenbank-Rahmen bildet und worüber die Datenbank später erstellt wird.
Dim table(x) As New ADOX.Table
Dieser Befehl erstellt ein Objekt, das den Tabellenrahmen bildet. Statt dem x kommt die Anzahl der Tabellen, die ihr erstellen wollt.
Dim column(x) As New ADOX.Column
Dieser Befehl erstellt ein Objekt, worüber ihr die Spalten und deren Eigenschaften festlegen könnt. x wird dabei durch die Anzahl aller Spalten ersetzt.
Dim idx(x) As New ADOX.Index
Dieser Befehl erstellt ein Objekt, worüber ihr dann Primärschlüssel und Indizies in den Spalten setzen könnt.
Dim relation(x) As New ADOX.Key
Dieser Befehl erstellt ein Objekt, womit ihr die Beziehungen zwischen Tabellen setzen könnt.
Dim dbhandle As String
In diesem String werden alle Angaben "gesammelt", die zur Erstellung der Datenbank notwendig sind.
Anmerkung: Es gibt noch eine andere, von der Ausführung schnellere Variante zum Erstellen der Datenbank, diese werde ich zum Schluss noch erwähnen.
Kommen wir wieder zurück zum Aufruf des Moduls. Ein Modul kann, wie jedes Formular, eine ganze Menge Subroutinen beinhalten. Jedoch gibt es im Gegensatz dazu ein kleinen, aber wichtigen Unterschied. Die Subroutine muss öffentlich gemacht werden, damit die Formulare wissen, das im Modul etwas vorhanden ist. Dazu gibt es zwei Möglichkeiten
Public Sub createdb()
oder
Public Sub createdb(dbfile As String)
Allgemein muss die Subroutine als Public gekennzeichnet werden. Private Subs sind nur innerhalb des Moduls bekannt und können nicht von außen aufgerufen werden (sog. Prinzip der Verkapselung).
Wo besteht nun der genaue Unterschied zwischen Variante 1 und Variante 2? Ganz einfach, es ist die Anzahl der Parameter, die erwartet werden, um die Subroutine aufzurufen. Variante 1 erwartet keine Parameter, Variante 2 erwartet die Variable dbfile, die als String übergeben werden muss (lässt man As String weg, kann der Typ der Variable jeden beliebigen Wert besitzen). Um mehr als einen Parameter zu fordern, schreibt man die Variablen, mit Komma getrennt, hintereinander.
Ich benutze im weiteren Verlauf Variante 2, werde aber an den wichtigen Stellen erklären, wie es auch mit Variante 1 geht.
Im Programmcode des Formulars gibt es auch wieder 2 Varianten, die Subroutine createdb aufzurufen.
Call modDB.createdb(dbfile)
oder
Call createdb(dbfile)
Für Variante 1 gilt:
Call modDB.createdb
oder
Call createdb
Variante 1 ist die längere Schreibweise zum Aufruf der Subroutine im Modul. Sie empfiehlt sich vor allem, wenn man mehrere Module schreibt, damit man immer den Überblick hat, welches Modul gerade angesprochen wird. Sie wird zu einem Muss, wenn man in mehreren Modulen den gleichen Namen für eine Subroutinen verwendet.
Ich erkläre euch jetzt, was es mit der Variable dbpath auf sich hat, und wie wir sie mit Werten füllen. Diese Variable soll uns den Pfad zur Anwendung liefern, den wir später brauchen, um die Datenbank in demselben Verzeichnis zu erstellen. Alternativ könnte man auch eine Benutzereingabeaufforderung basteln, worauf ich hier aber nicht eingehen werde. Dies werdet ihr euch selber zusammenbasteln müssen.
Das Formular
Begeben wir uns also erstmal zu unserem Formular:
Wir suchen nach:
Private Sub Form_Load()
Dort tragen wir ein:
findpath = App.Path If Right(findpath, 1) <> "\" Then findpath = findpath & "\" dbfile = findpath & "Datenbank.mdb" If Dir(dbfile) = "" Then Call modDB.createdb(dbfile) End If
App.Path ist die Methode, die uns das aktuelle Verzeichnis ausgibt, wo die Anwendung gestartet wurde. Diesen übergeben wir der Variable findpath. Nun hat diese Methode einen winzigen Haken. Ist das Anwendungsverzeichnis C:\, D:\, H:\ oder sonstirgendwie ein Stammverzeichnis, wird der Schrägstrich nicht mit angegeben.
Deswegen überprüfen wir mit Hilfe der zweiten Zeile, ob das letzte Zeichen ein \ beinhaltet oder nicht. Wenn nein, dann wird an den String findpath eins angehangen. Nun erstellen wir die Variable dbfile. Sie setzt sich zusammen aus dem findpath-String und den Namen der Datenbank (Ich habe sie einfach mal "Datenbank.mdb" genannt). Damit wir nicht ins Straucheln kommen und bei jedem Programmaufruf versucht wird, die Datenbank zu erstellen, überprüfen wir mit Hilfe des Befehles Dir, ob die Datenbank evtl. bereits vorhanden ist. Wenn Sie vorhanden ist, gibt Dir(dbfile) den Namen der Datenbank zurück, ansonsten einen leere Zeichenkette. Also prüfen wir, ob die Zeichenkette leer ist. Wenn ja, rufen wir sofort das Modul auf. Wenn nein, dann wird normal im Programm fortgefahren.
Spätestens hier wird man sehen, welche Vorteile Variante 2 gegenüber Variante 1 und welche Vorteile die Auslagerung des Codes hat.
Fassen wir noch einmal kurz zusammen: ihr habt gelernt, wie man die nötigen Objekte zur Datenbankerstellung erzeugt, warum man diesen Teil des Programms in ein Modul auslagern sollte und wie man dieses Modul aufruft..
Damit habt ihr erstmal die Werkzeuge geschaffen...
dbhandle = "Provider=Microsoft.Jet.OLEDB.4.0;" dbhandle = dbhandle & "Data Source=" & dbfile & ";" db.Create dbhandle
Was bedeutet dieser Code? Nun, als Erstes legen wir fest, wer unser Dolmetcher zwischen Access und VB ist. Ab Access 2000 ist der Dolmetscher Jet OLEDB 4.0, für Access 97 Jet OLEDB 3.5. In der zweiten Zeile erweitern wir unseren String um die Datenquelle, wo die Datenbank abgelegt werden soll. Hier kommt nun die Variable dbfile ins Spiel.
Sollte man keine Parameter verwendet haben, der muss hier im Modul prüfen nach Verzeichnis und Vorhandensein. Wer seine Datenbank mit einem allg. Kennwort versehen will, der muss dies noch anhängen:
dbhandle = dbhandle & "Jet OLEDB:Database Password=test;"
Mit diesen wenigen Zeilen haben wir unsere Datenbank erstellt. Sie ist zwar noch leer, aber immerhin schon vorhanden. Nun, eine Datenbank ist zwar gut und schön, aber ohne Tabellen und Spalten nicht wirklich sinnvoll. Kommen wir nun zur Tabellenerstellung.
table(0).Name = "Tabelle1" db.Tables.Append table(0)
In der ersten Zeile weisen wir der Feldvariable (Felder beginnen generell mit 0 statt mit 1) table den ersten Wert zu zu. Eine Tabelle braucht natürlich einen Namen, in diesem Fall einfach "Tabelle1". In der zweiten Zeile weisen wir dem Objekt db diese Variable zu. Somit wäre die erste Tabelle in der Datenbank erstellt. Da sie aber noch ziemlich nackt aussieht, versteckt sie sich vor unseren neugierigen Augen. Wir müssen ihr erst die passende Kleidung, d. h. die Spalten, geben, damit wir sie in Access zu Gesicht bekommen. Deshalb erstellen wir gleich die erste Spalte, sie soll vom Typ Autowert sein und fortan "Spalte1" genannt werden.
Set column(0).ParentCatalog = db column(0).Name = "Spalte1" column(0).Type = adInteger column(0).Properties("AutoIncrement") = True db.Tables("Tabelle1").Columns.Append column(0)
Eine Besonderheit ist, um Spalten zu erstellen, das der Spalte der Eigenschaftskatalog der zu erstellenden Datenbank hinzugefügt werden muss. Danach kommt wie bei der Tabelle gewohnt, der Name der Spalte.
In der nächsten Zeile wird der Typ der Datenbank festgelegt. Ich werde hier kurz auf die wichtigsten Typen und deren Bedeutung eingehen:
Typ Zahl
adInteger = ganze Zahl, Größe darf von -32768 bis +32767 sein
adBigInt = ganze Zahl deren Größe jenseits von gut und böse liegt, genauer gesagt von -2147483648 bis +2147483647.
adTinyInt = das Gegenstück vom großen Bruder, Zahl darf zwischen -128 und +127 liegen.
adSingle = Fließkommazahl mit einfacher Genauigkeit (vier Bytes, wie Single in VB).
adDouble = Fließkommazahl doppelter Genauigkeit (acht Bytes, wie Double in VB).
Typ Ja/Nein
adBoolean = Zahl, deren Wert nur ja/nein ist. In Access wird sie mit 0 oder -1 ausgedrückt, wenn man sie mit VB übergeben will.
Typ Text
adChar = Standard-ASCII Zeichensatz mit fester Größe von 256 Zeichen.
adVarChar = Standard-ASCII-String darf übergeben werden, hier kann die Länge zwischen 0 und 256 variieren.
adVarWChar = wie adVarChar, allerdings dürfen alle Zeichen aus sämtlichen Schriftarten verwendet werden.
Typ Memo
adLongVarChar = wie adVarChar, allerdings kann die Zeichengröße 65.536 lang sein
adLongVarWChar = wie adVarWChar, darf auch 65.536 Zeichen lang sein.
Typ Datum/Uhrzeit
adDate = erwartet einen String im Datumsformat mit Uhrzeit, allerdings weiß ich nicht, wie er aussehen muss (da hilft experimentieren). Wenn man in den Eigenschaften einen Standardwert definiert, ist diese Funktion sicherlich sehr hilfreich (zu den Eigenschaften komme ich danach).
Die folgenden Eigenschaften werden meines Wissens automatisch generiert:
adDBDate = wie adDate, nur das man das Datum bekommt, wo die Datenbank liegt.
adDBTime = damit bekommt man die Uhrzeit des Rechners,auf dem die Datenbank lieg im hh::mm:ss-Format.
adDBTimeStamp = damit bekommt man die Uhrzeit im UNIX-Format. Es ist eine 32bitZahl, die die Anzahl der vergangenen Sekunden vom 1.1.1970 bis jetzt enthält.
Es gibt etwa nochmal soviele Eigenschaften, die ich hier nicht aufgezählt habe, da ich sie nicht kenne und nicht weiß, ob es in Access unterstützt wird. Jedoch sollten vorerst die geläufigsten reichen. ihr werdet sicherlich festgestellt haben, das ein Typ fehlt. Richtig, der AutoWert wird nicht im Typ festgehalten, sondern muss in den Eigenschaften notiert werden. Eigenschaften der Spalte legt ihr über die Properties-Eigenschaft fest. Der AutoWert lautet AutoIncrement . Damit kein Fehler entsteht, muss der Typ adInteger, adSmallInt, adTinyInt oder adBigInt lauten.
Damit wäre die erste Spalte bereits fertig erstellt und muss nur noch zum Datenbankobjekt hinzugefügt werden. Dies machen wir in der letzten Zeile.
Nun, wie ihr seht, ist es schon etwas mehr Tipparbeit, um eine einzige Spalte zu erstellen. Damit ihr euch etwas Tipparbeit ersparen könnt, stelle ich euch eine alternative Schreibweise vor, die ich im weiteren Verlauf verwende (da ich auch etwas tippfaul bin).
With column(0) Set .ParentCatalog = db .Name = "Spalte1" .Type = adInteger .Properties("AutoIncrement") = True End With db.Tables("Tabelle1").Columns.Append column(0)
Wie ihr seht, taucht die Feldvariable column(0) nur am Anfang und am Ende auf. Wenn man sich bei der objektorientierten Programmierung in einem zusammenhängenden Block sich auf ein und dasselbe Objekt bezieht, darf man dies in With / End With einschließen.
Dazu gilt:
- Die Notation With muss den Objektnamen beinhalten, der weggelassen werden darf.
- Ein mit With begonnener Block muss mit End With abschließen.
- With-Anweisungen dürfen zwar verschachtelt werden, doch kann in den Ebenen keine Werte der Eigenschaft von höheren Ebene festgelegt werden.
- In mit With eingeschlossene Blöcke können auch andere Objekte bearbeitet werden, allerdings ohne Abkürzung.
Wenn ihr euch an diese Regeln haltet, werdet ihr euch eine Menge Arbeit ersparen können, da die Erstellung einer Datenbank eine Menge Tipparbeit beinhaltet. Fahren wir nun fort. Zwar haben wir jetzt eine Spalte erstellt, doch eine Sache fehlt noch, nämlich der Index (Primärschlüssel sind auch Indizies).
With idx(0) .Name = "Primaerschluessel" .IndexNulls = adIndexNullsDisallow .PrimaryKey = True .Unique = True .Columns.Append "Spalte1" End With db.Tables("Tabelle1").Indexes.Append idx(0)
Mussten wir bei den Spaltenbeschreibungen schon etwas mehr schreiben, so legen wir bei den Indizies noch etwas drauf. Vertraut mir, dies war noch lange nicht der Höhepunkt. Die ersten zwei Zeilen sollten sich mittlerweile von selbst erklären. Bei der Eigenschaft IndexNulls gibt es genau vier Werte, die eingetragen werden können.
adIndexNullsAllow = Spalten ohne Werte sind erlaubt.
IndexNullsDisallow = Spalten müssen mit Werte gefüllt werden.
IndexNullsIgnore = leere Spalten werden ignoriert und bei einer Abfrage nicht mit übergeben.
IndexNullsIgnoreAny = wie IndexNullsIgnore, nur konsequenter, da auch leere Spalten akzeptiert werden, obwohl dadurch ein Fehler verursacht wird.
Ein Primärschlüssel fordert, das die Spalte unbedingt mit Werten gefüllt werden muss. Also muss die Eigenschaft dafür immer den Wert adIndexNullsDisallow haben. Die nächste Zeile deklariert den Index als Primärschlüssel. Die Eigenschaft Unique bedeutet, ob der Wert in der Spalte nur einmal vorkommen darf (indiziert: ja, ohne Duplikate). Da wir einen Primärschlüssel erstellen wollen, ist der Wert True Pflicht!
Nun müssen wir noch festlegen, zu welcher Spalte der Primärschlüssel gehören soll und dies dann wie gewohnt, in der letzten Zeile, dem Datenbankobjekt übergeben. Eine Tabelle mit einer Spalte bringt sicherlich nicht viel, deswegen erstellen wir eine gleich eine zweite Spalte. Diese soll vom Typ Textfeld sein und eine maximale Größe von 20 Zeichen beinhalten. Die Werte, die eingetragen werden sollen, dürfen nicht noch einmal vorkommen und es Pflicht, Werte zu übergeben.
With column(1) Set .ParentCatalog = db .Name = "Spalte2" .Type = adVarChar .DefinedSize = 20 End With db.Tables("Tabelle1").Columns.Append column(1) With idx(1) .Name = "Index1" .IndexNulls = adIndexNullsDisallow .PrimaryKey = False .Unique = True .Columns.Append "Spalte2" End With db.Tables("Tabelle1").Indexes.Append idx(1)
Ich denke, das ich hierzu nicht viel mehr sagen muss. Es ist eine neue Objekteigenschaft hinzugekommen. DefiniedSize bestimmt die maximale Länge der Spalte. Dies kann man nur in Textfeldern angeben. Sonst gibt es schöne Fehlermeldung. Beim Index sollte sich mit dem jetzigen Wissensstand alles von selbst erklären.
Nun kommen wir langsam zum Schluß des Tutorials. Es fehlt nur noch das Setzen von Beziehungen zwischen den Tabellen. Damit wir eine Beziehung überhaupt aufbauen können, fehlt uns also noch eine zweite Tabelle.
Also gehen wir an die Arbeit. Wir erstellen eine zweite Tabelle mit Namen "Tabelle2" und 3 Spalten. Die erste Spalte bekommt den Namen "Spalte3", ist Typ Autowert und bekommt einen Primärschlüssel. Die zweite Spalte bekommt den Namen "Spalte1" (Spaltennamen sollten gleich sein, wenn eine Beziehung darüber aufgebaut wird. Der Spaltentyp muss gleich sein), ist vom Typ Zahl. Die letzte Spalte soll vom Typ Memo sein und als Standardwert soll automatisch "dieses Feld ist noch nicht belegt" angelegt werden. Außerdem soll ein Index angelegt werden, womit Duplikate möglich sind.
table(1).Name = "Tabelle2" db.Tables.Append table(1) With column(2) Set .ParentCatalog = db .Name = "Spalte1" .Type = adInteger .Properties("AutoIncrement") = True End With db.Tables("Tabelle1").Columns.Append column(2) With idx(2) .Name = "PrimaryKey" .IndexNulls = adIndexNullsDisallow .PrimaryKey = False .Unique = True .Columns.Append "Spalte3" End With db.Tables("Tabelle2").Indexes.Append idx(2) With column(3) Set .ParentCatalog = db .Name = "Spalte1" .Type = adInteger End With db.Tables("Tabelle2").Columns.Append column(3) With column(4) Set .ParentCatalog = db .Name = "Spalte 4" .Type = adLongVarChar .Properties("Default") = "Dieses Feld ist noch nicht belegt." End With db.Tables("Tabelle2").Columns.Append column(4) With idx(3) .Name = "PrimaryKey" .IndexNulls = adIndexNullsDisallow .PrimaryKey = False .Unique = False .Columns.Append "Spalte4" End With db.Tables("Tabelle2").Indexes.Append idx(3)
Das meiste dürfte sich wieder von selbst erklären zwinkern. Jetzt dürfte auch einigen von euch einleuchten, warum es kein expliziten Typ Autowert im Column-Objekt gibt, da Autowert nur eine Eigenschaft ist, die den Typ Zahl hochzählt.
Mit der Eigenschaft Properties("Default") kann man festlegen, was als Standard in die Spalte eingetragen werden soll, wenn man dies in seiner Abfrage nicht angibt.
With relation(0) .Name = "Beziehung1" .Type = adKeyForeign .RelatedTable = "Tabelle1" .Columns.Append "Spalte1" .Columns("Spalte1").RelatedColumn = "Spalte1" .DeleteRule = adRICascade End With db.Tables("Tabelle2").Keys.Append relation(0)
Dieser Code definiert automatisch eine 1:n-Beziehung mit der Option, das alle Datensätze aus Tabelle 2 gelöscht werden sollen, die mit dem Datensatz aus Tabelle 1 verbunden sind. Die Beziehung bekommt wie üblich einen Namen. Der Typ für eine Beziehung ist immer adKeyForeign. Um eine Beziehung aufzubauen, bezieht man sich erst auf die Tabelle und die Spalte wo der Startpunkt ist. Wenn man sich angewöhnt hat, bei den zu verbindenden Spalten gleiche Namen zu vergeben, der hat wenig Probleme bei dem Befehl Columns("Spalte1").RelatedColumn ="Spalte1". Das erste "Spalte1" ist der Startpunkt, das zweite der Endpunkt der Beziehung.
Will man Datensätze in verbundenen Spalten löschen, setzt man die Eigenschaft .DeleteRule auf adRICascade, will man aktualisieren, nimmt man dafür .UpdateRule = adRICascade. Man kann auch beide Objekteigenschaften zusammen in eine Beziehung einbauen. Wenn man weder das Eine noch das Andere vorhat, lässt man diese Objekteigenschaften einfach weg. Nun sind wir auch schon am Ende des Tutorials angekommen. Es dürfte jetzt eigentlich alles erwähnt sein, was wichtig ist, um die Datenbank zu erstellen.
Zum Schluss möchte ich noch auf die von der Geschwindigkeit her schnellere Lösung eingehen.
Option Explicit Dim db As ADOX.Catalog Dim table As ADOX.table Dim column As ADOX.column Dim idx As ADOX.Index Dim relation As ADOX.Key Dim dbhandle As String
Wie ihr seht, sind die Variablen keine Felder mehr und werden nicht mehr mit As New deklariert.
Public Sub createdb(dbfile As String) Set db = New ADOX.Catalog dbhandle = "Provider=Microsoft.Jet.OLEDB.4.0;" dbhandle = dbhandle & "Data Source=" & dbfile & ";" db.Create dbhandle Set table = New ADOX.Table table.Name = "Tabelle1" db.Tables.Append table Set column = New ADOX.Column With column Set .ParentCatalog = db .Name = "Spalte1" .Type = adInteger .Properties("AutoIncrement") = True End With db.Tables("Tabelle1").Columns.Append column Set idx = New ADOX.Index With idx .Name = "Primaerschluessel" .IndexNulls = adIndexNullsDisallow .PrimaryKey = True .Unique = True .Columns.Append "Spalte1" End With db.Tables("Tabelle1").Indexes.Append idx Set column = New ADOX.Column With column Set .ParentCatalog = db .Name = "Spalte2" .Type = adVarChar .DefinedSize = 20 End With db.Tables("Tabelle1").Columns.Append column Set idx = New ADOX.Index With idx .Name = "Index1" .IndexNulls = adIndexNullsDisallow .PrimaryKey = False .Unique = True .Columns.Append "Spalte2" End With db.Tables("Tabelle1").Indexes.Append idx
Anhand dieses Beispielcodes seht ihr den Unterschied, ihr müsst zuerst das Objekt, auf das ihr euch bezieht setzen mit dem Befehl Set ... = New ... Dies müsst vor allem bei den ganzen Indizies, Spalten und Beziehungen immer wieder erneut machen, da ihr sonst eine Fehlermeldung bekommt, das das Objekt schon vorhanden ist. Allerdings hat diese Methode den (fraglichen) Vorteil, dass die Datenbank schneller erstellt wird. Fraglich deshalb, weil man mehr Schreibaufwand hat und man die Zeit bis die Datenbank fertig erstellt ist, auch überbrücken kann. Wie ihr das am Ende anstellt, ist euch selbst überlassen.
Viel Spaß bei der Datenbankerstellung!
Ihre Meinung
Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.