#Gelöscht in Datenfeldern

28. Dezember 2015

Als Datenbankentwickler mit Access ist man immer wieder mit der Situation konfrontiert, dass Access in den Datenzeilen statt der erwarteten Tabelleninhalte #Gelöscht anzeigt oder dass nach dem Speichern von neuen Datensätzen falsche Daten angezeigt werden. Das sieht dann zum Beispiel so aus (Bild zeigt die englische Variante):

Statt der Daten wird #Gelöscht angezeigt

In den meisten Fällen, in denen Access statt der gewünschten Daten ein #Gelöscht anzeigt, gibt es eine einfache Ursache: Access speichert das Ergebnis einer Datenbankabfrage meist in sogenannten Dynasets. Das bedeutet, dass nicht die kompletten Datenbankinhalte des entsprechenden Recordsets sondern nur die entsprechenden PrimaryKeys gespeichert werden, was natürlich viel weniger Speicherplatz und Zeit bei der Datengenerierung benötigt. Erst wenn man sich die entsprechende Daten zum Beispiel durch Herunterscrollen wirklich anschauen möchte, werden die zu einem PrimaryKey gehörenden Daten aus der Datenbank ausgelesen.

Das funktioniert jedoch nur dann gut, wenn für alle Tabellen "vernünftige" PrimaryKeys definiert sind. Vernünftig bedeutet in dem Zusammenhang, dass für jede Tabelle eine Autowert-Spalte als Zähler mit der PrimaryKey-Eigenschaft eingerichtet wird. Alle anderen PrimaryKeys wie zum Beispiel Fließkommazahlen oder Strings sind eigentlich nicht geeignet. Weitere eindeutige Schlüssel, sogenannte CandidateKeys, können unabhängig davon noch zusätzlich definiert werden. Der für die Autowert-Spalte benötigte zusätzliche Speicherplatz spielt auf heutigen Computersystemen denke ich keine Rolle mehr.

Mit der oben beschriebenen Regel sind die allermeisten Probleme in Bezug auf die #Gelöscht-Datensätze behoben. Es gibt jedoch insbesondere im Zusammenspiel mit SQL-Servern ganz hartnäckige Fälle, in denen nach dem Speichern eines Datensatzes entweder #Gelöscht oder auch ein komplett anderer Datensatz angezeigt wird. Dies zeigt der untenstehende Screenshot:

Ein neu angelegter Datensatz wird als gelöscht angezeigt

Ich habe schon Stunden erfolglos damit zugebracht, eine Systematik und daraus resultierend eine Umgehung für dieses Problem zu finden. Am Ende habe ich akzeptiert, dass beim Anlegen eines neuen Datensatzes die korrekte ID des neuen Datensatzes aus irgendeinem Grund nicht den Weg zurück zu Access-Applikation findet, sondern Access von einem falschen Wert ausgeht. Da diese Beobachtung eigentlich nur bei Tabellen mit verhältnismäßig vielen Datensätzen gemacht wurde, vermute ich irgendeine Form von Überlauf in der Datenbankschnittstelle.

Ich habe in meiner Not dann etwas eigentlich Unschönes gemacht, das in meinen Applikationen das Problem aber zum Verschwinden gebracht hat: Vor Aktualisierung des Datensatzes bestimme ich von der Applikation aus den nächsten freien Autowert. Dies ist eigentlich die primäre Aufgabe der Datenbank, führt aber, wie oben beschreiben, zu einem Fehler. Da es sein kann, dass genau in demselben Augenblick ein anderer Client ebenfalls den nächsten freien Autowert bestimmen will, kopple ich diesen Vorgang noch mit einer Sperrfunktion, so dass immer nur ein Client zur Zeit einen neuen Autowert bestimmen kann. Ist die Funktion gesperrt, warte ich einen Moment und probiere es dann noch einmal. Da die Anzahl der Benutzer in den Applikationen, in denen dieser Workaround zum Einsatz kommt, nicht zu groß ist, hat es hier noch nie Probleme gegeben. Das ist zwar keine wirklich schöne Programmierung, aber manchmal heiligt der Zweck halt die Mittel.

Jetzt aber noch einmal im Detail: Im Event-Handler des Ereignisses Vor Aktualisiertung weise ich dem Feld mit der ID den selbst ermittelten ID-Wert zu. Etwa so:

FTei!AufTei_ID = AutoWert("Auftraege_Teile", "AufTei_ID")

Die Funktion Autowert selbst sieht so aus:

Function AutoWert(Tabelle As String, Autowertspalte As String, Optional Sperr As Boolean = True) As Long
'
' Nach dem Speichern eines Datensatzes wird dieser noch einmal neu geladen. Manchmal passieren dabei Fehler,
' was sich in #gelöscht-Einträgen oder einfach in einem falsch angezeigten Datensatz mainfestiert. Darum wird der
' Wert des Zählerfeldes vor dem Speichern bestimmt.
'
Dim SName As String ' wird für die Funktion Sperren benötigt
Dim MaxID As Variant
Dim I As Integer
  
  GlobVarSetzen
   
  ' Sperrung für das Erzeugen eines Autowertes erzeugen
  If Sperr Then
    For I = 1 To 10
      If (Sperren("Autowert " & Tabelle, SName)) Then
        Exit For
      End If
     
      ' 2 Sekunden warten
      sSleep 2000
      
      If I = 10 Then
        Err.Raise 12345, "AutoWert()", "Es kann kein neuer Autowert für Tabelle " & Tabelle & " erzeugt werden."
        Exit Function
      End If
    Next I
  End If
  
  MaxID = DMax(Autowertspalte, Tabelle)
  
  If IsNull(MaxID) Then
    AutoWert = 1
  Else
    AutoWert = MaxID + 1
  End If
  
  EntSperren "Autowert " & Tabelle
  
End Function