Datenzugriff im Schichtbetrieb

Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.

Autor: Christoph Spielmann, Düsseldorf

!

Was Access von anderen Entwicklungsumgebungen unterscheidet, ist insbesondere die einfache und schnelle Entwicklung kompletter Anwendungen ohne viel Programmcode. Eine einfache Datenbank ist so in vielen Fällen bereits nach wenigen Stunden in den Grundzügen fertig gestellt. Sobald die Datenbank aber wächst, werden die ersten Schwächen und Probleme deutlich. Dieser Artikel zeigt Ihnen anhand eines Beispiels für Access 2000, wie Sie diese Schwächen vermeiden.

Der typische Aufbau einer Access-Anwendung sieht folgendermaßen aus: Sie haben verschiedene Tabellen, die – teils mit oder teils ohne Abfragen – an Formulare gebunden sind.

Innerhalb der Formulare werden die Daten angezeigt, verändert und neu hinzugefügt. Beim Verändern und Hinzufügen prüft ein Makro oder eine VBA-Prozedur, ob die Eingaben bestimmten Plausibilitätsregeln entsprechen.

Teilweise wird hierzu auf Werte anderer Formulare zugegriffen und teilweise findet eine Validierung auch innerhalb der Gültigkeitsregel-Eigenschaft von Tabellen oder Formularfeldern statt.

Wenn Ihre Datenbank ein zweites Formular enthält, in dem Sie die gleichen Daten evtl. tabellarisch bearbeiten, dann bringen Sie hier einen Großteil der Logik nochmals unter. Bei änderungen passen Sie dann notwendigerweise beide Formulare an.

Diese Art der Anwendungsentwicklung funktioniert bis zu einem bestimmten Punkt sehr gut, da Ergebnisse relativ zügig erzielt werden können.

Wächst die Datenbank aber weiter, zeigen sich sehr schnell die Schwächen:

  • änderungen müssen häufig an vielen Stellen parallel durchgeführt werden.
  • Wenn man an einer Stelle eine Kleinigkeit ändert, wirkt sich dies oft auf viele andere Stellen der Datenbank aus. Die Folge sind viele Fehler und eine wenig robuste Datenbank.
  • Da die Geschäftslogik über viele Makros, Module, Abfragen und Formulare verstreut ist, ist die Pflege zeitintensiv. Zudem können fremde Entwickler nur sehr schwer die Funktionsweise der Datenbank nachvollziehen.
  • Der Entwickler kann leicht die übersicht über die Software verlieren, sodass sich weitere Fehler einschleichen.
  • Eine solche Software-Architektur wird als monolithisch bezeichnet, da sich alle Funktionen durcheinander in einem Klotz befinden.

    Die Lösung des Problems besteht darin, die Anwendung nach dem Schichtenmodell aufzubauen. Hierbei werden häufig drei Schichten verwendet: Datenschicht, Business-Schicht und Präsentationsschicht.

    In der Datenschicht sind Funktionen untergebracht, die lesend oder schreibend auf die Tabellen zugreifen. Jeglicher Datenzugriff muss über diese Schicht erfolgen.

    In der Business-Schicht werden Plausibilitäten geprüft. Dazu gehört beispielsweise die Prüfung, ob der aktuelle Benutzer die betreffende Funktion ausführen darf oder ob alle Datenfelder korrekte Werte beinhalten. Die Business-Schicht greift zur Prüfung ausschließlich auf die Datenschicht zu.

    An oberster Stelle liegt die Präsentationsschicht. Hier werden Daten erfasst oder angezeigt. Die Präsentationsschicht darf nur auf die Business-Schicht, nicht jedoch auf die Datenschicht zugreifen.

    Die Vorteile liegen auf der Hand: Wenn Sie in der Business-Schicht eine Prüfung eines Schreibvorgangs unterbringen und alle Formulare dieses Funktion nutzen, brauchen Sie auch nur eine zentrale Funktion anpassen bzw. korrigieren.

    Ein anderer Vorteil besteht darin, dass Sie beim Umstieg z. B. auf den SQL-Server lediglich die Datenschicht anpassen müssen. Die Business-Schicht, in der das eigentliche Know-how Ihrer Anwendung liegt, kann dagegen unverändert bleiben.

    Das hier vorgestellte Beispiel besteht aus nur einer einzelnen Tabelle, die Adressdaten zusammen mit einem Anlage- und einem änderungsdatum speichert.

    Die Beispieldatenbank finden Sie auf der Heft-CD unter dem Dateinamen Klassen2000.mdb.

    Die Tabelle tblAdressen besteht aus den folgenden Feldern (siehe Bild 1):

  • AdresseID (Long Integer, Primärschlüssel der Tabelle)
  • Firmenname (Text)
  • Nachname (Text)
  • Vorname (Text)
  • Straße (Text)
  • PLZ (Text)
  • Ort (Text)
  • Anlagedatum (Datum/Uhrzeit)
  • änderungsdatum (Datum/Uhrzeit)
  • Bild 1: Die Tabelle tblAdressen in der Entwurfsansicht.

    Standardwerte oder Gültigkeitsregeln sind nicht definiert, da diese Aufgaben von der Business-Schicht erledigt werden.

    Hinweis

    Parallel zu der Beispieldatenbank Klassen2000.mdb finden Sie noch eine weitere Datenbank namens KlassenXP.mdb, die die Vorgehensweise beim Einsatz von XP beschreibt. Access 2000 enthält einige Fehler die bei der Bindung von Recordsets an Formulare zutage treten. Bei Access XP sind diese Probleme behoben, sodass hier einige Prozeduren einen einfacheren und eleganteren Aufbau haben.

    Zum Zugriff auf die Tabellen ist nun zunächst einmal die Anlage der Datenschicht erforderlich.

    Um eine gute übersicht zu gewährleisten, sollte pro Tabelle ein entsprechendes Klassenmodul angelegt werden, in dem alle Funktionen untergebracht sind, die sich mit dieser Tabelle beschäftigen.

    Public Function GetAdresse(AdresseID As Long) _    As Recordset
        Dim SQL As String
        Dim Data As New ADODB.Recordset
        SQL = "SELECT * FROM tblAdressen"
        SQL = SQL & " WHERE AdresseID=" & CStr(AdresseID)
        With Data
            .ActiveConnection = CurrentProject.Connection
            .Source = SQL
            .LockType = adLockReadOnly
            .CursorType = adOpenStatic
            .CursorLocation = adUseClient
            .Open
        End With
        Set GetAdresse = Data
    End Function

    Quellcode 1

    Bild 2: Der Zugriff auf die Tabellen erfolgt ausschließlich über das Klassenmodul AdressenD.

    Da in diesem Beispiel nur eine Tabelle zum Einsatz kommt, existiert auch nur ein Klassenmodul. Dies trägt den Namen AdressenD. Das D steht hierbei für Datenschicht (siehe Bild 2).

    Einzelne Adresse abrufen

    Die erste Prozedur dieses Klassenmoduls trägt den Namen GetAdresse. Ihre Aufgabe ist es, eine einzelne Adresse aus der Tabelle abzurufen und die Daten in Form eines Recordsets zurück zu liefern. Als Parameter erwartet die Prozedur die Primärschlüsselnummer der Adresse (s. Quell-code 1).

    Innerhalb der Prozedur wird ein SQL-Text zusammengestellt mit dessen Hilfe danach ein Recordset gefüllt wird. Dieses wird dann als Ergebnis der Funktion zurückgegeben. Damit sich änderungen am Recordset, die in einer höheren Schicht durchgeführt werden, nicht auf die Datenbank auswirken, sollte das Recordset vor der übergabe an die höhere Schicht noch entbunden (disconnected) werden:

    Set .ActiveConnection = Nothing

    Schließlich sollen Schreibzugriffe kontrolliert über eine andere Datenschicht-Funktion durchgeführt werden.

    Aufgrund eines Fehlers in Access 2000 lässt sich ein solches Recordset jedoch nicht an ein Formular binden, weshalb auf diese Anweisung in dieser Beispieldatenbank generell verzichtet werden muss. Beim Einsatz von Access XP tritt das Problem allerdings nicht mehr auf.

    Um direkte Schreibzugriffe zu verhindern, wird die LockType-Eigenschaft auf ReadOnly gestellt.

    Der LockType wird bewusst nicht auf ReadOnly gesetzt. Dies hat den folgenden Grund:

    Public Function GetAdressenSuchergebnis(Firmenname As _    String, Nachname As String) As Recordset
        Dim SQL As String
        SQL = "SELECT AdresseID, Firmenname, Nachname, " _    SQL = SQL & "Vorname, PLZ FROM tblAdressen"
        SQL = SQL + " WHERE True"
        If Firmenname <> "" Then
            SQL = SQL + " AND Firmenname LIKE " + _            Quote("%" + Firmenname + "%")
        End If
        If Nachname <> "" Then
            SQL = SQL + " AND Nachname LIKE " + _            Quote("%" + Nachname + "%")
        End If
        Dim Data As New ADODB.Recordset
        With Data
            .ActiveConnection = CurrentProject.Connection
            .Source = SQL
            .LockType = adLockReadOnly
            .CursorType = adOpenStatic
            .CursorLocation = adUseClient
            .Open
          End With
        Set GetAdressenSuchergebnis = Data
    End Function

    Quellcode 2

    Public Function SaveAdresse(Firmenname As String, _
        Nachname As String, Vorname As String, _
        Strasse As String, PLZ As String, Ort As String, _
        Anlagedatum As Date, änderungsdatum As Date) As Long
        Dim SQL As String
        SQL = "SELECT * FROM tblAdressen WHERE False"
        Dim Data As New ADODB.Recordset
        With Data
            .ActiveConnection = CurrentProject.Connection
            .Source = SQL
            .LockType = adLockOptimistic
            .CursorType = adOpenDynamic
            .MaxRecords = 1
            .Open
            .AddNew
                !Firmenname = ZN(Firmenname)
                !Nachname = ZN(Nachname)
                !Vorname = ZN(Vorname)
                !Strasse = ZN(Strasse)

    Quellcode 3 (Fortsetzung nächste Seite)

    Das Recordset wird später auch in der Präsentationsschicht verwendet und hier an das entsprechende Access-Formular gebunden.

    Wäre hier ReadOnly eingestellt, könnte man die Daten des Formulars nicht bearbeiten (und dann letztendlich auch nicht an die entsprechende Speicher-Funktion weiterleiten).

    Abfragen eines Suchergebnisses

    Da die Anwendung auch eine Suchfunktion zur Verfügung stellt, liefert die Funktion GetAdressenSuchergebnis mehrere Adressen zurück, die einem Suchkriterium entsprechen (s. Quellcode 2). Auch in dieser Funktion wird wieder ein SQL-Text zusammengestellt und ein entsprechendes Recordset gefüllt. Um unbeabsichtigte änderungen am Recordset zu vermeiden, wird der LockType auf adLockReadOnly und der CursorType auf adOpenStatic gesetzt.

    Die Quote-Funktion hat die Aufgabe, evtl. vorhandene Anführungszeichen im Suchtext zu doppeln, sodass Access kein Problem bei der Auswertung der Abfrage hat. Sie finden die Prozedur im Modul modTools der Beispieldatenbank.

    Adresse hinzufügen

    Die beiden vorgestellten Prozeduren dienen lediglich dem Einlesen, aber nicht dem Speichern von Daten.

                !PLZ = ZN(PLZ)
                !Ort = ZN(Ort)
                !Anlagedatum = Anlagedatum
                !änderungsdatum = änderungsdatum
                SaveAdresse = !AdresseID
            .Update
            .Close
        End With
    End Function

    Quellcode 3 (Fortsetzung)

    Public Sub UpdateAdresse(AdresseID As Long, Firmenname _    As String, Nachname As String, Vorname As String, _    Strasse As String, PLZ As String, Ort As String, _    änderungsdatum As Date)
        Dim SQL As String
        SQL = "SELECT * FROM tblAdressen"
        SQL = SQL + " WHERE AdresseID=" + CStr(AdresseID)
        Dim Data As New ADODB.Recordset
        With Data
            .ActiveConnection = CurrentProject.Connection
            .Source = SQL
            .LockType = adLockOptimistic
            .CursorType = adOpenDynamic
            .Open
                !Firmenname = ZN(Firmenname)
                !Nachname = ZN(Nachname)
                !Vorname = ZN(Vorname)
                !Strasse = ZN(Strasse)
                !PLZ = ZN(PLZ)
                !Ort = ZN(Ort)
                !änderungsdatum = änderungsdatum
            .Update
            .Close
        End With
    End Sub

    Quellcode 4

    Public Sub DeleteAdresse(AdresseID As Long)
        Dim SQL As String
        SQL = "DELETE * FROM tblAdressen"
        SQL = SQL + " WHERE AdresseID=" + CStr(AdresseID)
        CurrentProject.Connection.Execute SQL
    End Sub

    Quellcode 5

    Die Prozedur SaveAdresse hat dagegen die Aufgabe, eine Adresse in die Tabelle zu schreiben (s. Quellcode 3). Die Prozedur hat für jedes Feld der Tabelle einen entsprechenden Parameter. Ausnahme ist der Primärschlüssel. Dieser wird nach Anlage als Ergebnis der Prozedur zurück geliefert.

    Die ZN-Funktion wandelt einen Leerstring in den Wert Null um. Dies ist erforderlich, da als Parameter der Datentyp String und nicht Variant verwendet wird.

    Vorhandene Adresse aktualisieren

    Die Update-Prozedur hat einen ähnlichen Aufbau (s. Quellcode 4). Im Unterscheid zur Anlage einer neuen Adresse wird bei dieser Prozedur kein Wert zurückgegeben.

    Die ID muss stattdessen als Parameter übergeben werden, sodass die Prozedur weiß, welche Adresse sie aktualisieren soll.

    Adresse löschen

    Zu guter Letzt fehlt noch eine Prozedur zum Löschen einer Adresse (s. Quellcode 5)

    Die Aufgabe der Business-Schicht ist es, den Aufruf der entsprechenden Datenschicht-Prozeduren zu koordinieren und die Datenbank vor unsinnigen Speichervorgängen zu schützen.

    Public Function GetAdresse(AdresseID As Long) As _    Recordset
        With New AdressenD
            Set GetAdresse = .GetAdresse(AdresseID)
        End With
    End Function

    Quellcode 6

    Public Function GetAdressenSuchergebnis(Firmenname As _    String, Nachname As String) As Recordset
        If Firmenname = "" And Nachname = "" Then
            Err.Raise vbObjectError + 2000, _            "UpdateAdresse", _            "Firmenname oder Nachname erforderlich"
        End If
        With New AdressenD
            Set GetAdressenSuchergebnis = _            .GetAdressenSuchergebnis(Firmenname, _            Nachname)
        End With
    End Function

    Quellcode 7

    Public Function SaveAdresse(Firmenname As String, _    Nachname As String, Vorname As String, Strasse As _    String, PLZ As String, Ort As String) As Long
        If Firmenname = "" And Nachname = "" Then
            Err.Raise vbObjectError + 2000, _           "UpdateAdresse", "Firmenname oder Nachname erforderlich"
        End If
        If PLZ = "" Then
            Err.Raise vbObjectError + 2000, _"UpdateAdresse", "Postleitzahl erforderlich"
        End If
        If Ort = "" Then
            Err.Raise vbObjectError + 2000, _"UpdateAdresse", "Ort erforderlich"
        End If
        With New AdressenD
            SaveAdresse = .SaveAdresse(Firmenname, _
                Nachname, Vorname, Strasse, PLZ, Ort, _            Now, Now)
        End With
    End Function

    Quellcode 8

    In diesem Beispiel trägt das entsprechende Klassenmodul den Namen AdressenB. Das B steht hierbei für Business-Schicht.

    Bei den öffentlichen Prozeduren der Business-Schicht bietet es sich an, wenn möglich die Namen der Datenschicht zu übernehmen. Dementsprechend trägt die erste Prozedur den Namen GetAdresse (s. Quellcode 6). Diese Prozedur macht nichts weiter, als eine neue Instanz der Datenschicht-Klasse AdressenD zu erzeugen und hier die gleichnamige Prozedur GetAdresse aufzurufen.

    Die Prozedur GetAdressenSuchergebnis hat einen ähnlich einfachen Aufbau (s. Quellcode 7). Hier wird jedoch bereits geprüft, ob entweder ein Firmenname oder ein Nachname angegeben ist. Dies soll vermeiden, dass der Benutzer zu viele Datensätze abfragt und die Performance der Datenbank darunter leiden würde.

    Sollten beiden Parameter Leerstrings enthalten, löst die Prozedur einen Laufzeitfehler mit einem entsprechenden Fehlertext aus. Als Fehlernummer wird hierbei der Wert 2000 zuzüglich dem Wert der Access-Konstanten vbObjectError verwendet.

    Der Wert 2000 könnte innerhalb Ihrer Anwendung als genereller Fehler der Business-Schicht interpretiert werden.

    Zusätzlich könnten Sie weitere Fehlernummern reservieren, um genauere Informationen über die Art des Fehlers an die höhere Schicht zu liefern.

    Bild 3: Das Suchformular in der Entwurfsansicht

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

    Testzugang

    eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel

    diesen und alle anderen Artikel mit dem Jahresabo

    Schreibe einen Kommentar