Session D-NETZ

Multiuser-Programmierung
in Visual FoxPro

Jürgen Wondzinski
ProLib Software GmbH


Einleitung

Durch ständig sinkende Hardwarepreise wurden Netzwerke nun auch in den kleinen Büros interessant, so daß nun viele zuerst als Einzelplatzanwendung geschriebene Programme auf den Multiuser-Betrieb umgestellt werden müssen. Diese Änderungen betreffen zum einen Anpassungen an neue Funktionen, die nur ein Netzwerk bietet (Drucker-Spooling, Transaction Tracking, Email), zum andern die durch Multiuser-Betrieb entstehenden Problematiken wie Datei- und Satzsperren usw.

Nicht zu vergessen auch all die Anwender, die nun unter Windows Ihre Applikation gleich mehrfach anstarten (sei's aus Schusseligkeit, oder gewollt). Auch dies bedingt Multiuser-Programmierung, auch wenn es auf einem einzigen Rechner stattfindet.

Anmerkung: In diesem Artikel verwende ich hauptsächlich die englischen Ausdrücke; zum Einen, weil die ganze Thematik sehr eng mit den sowieso englischen Programmierbefehlen verbunden ist, zum Anderen, weil die Eindeutschungen nicht wesentlich verständlicher sind.

Doch zuerst noch etwas graue Theorie....

Vom SingleUser zum MultiUser:

In der normalen Einzelplatzumgebung läßt sich's einfach programmieren: Man ist Herr der Dinge, während man einen Datensatz ändert, kann nichts anderes mit ihm passieren. Ganz anders ist die Situation im Netzwerk: Da mit Ihnen noch andere Stationen gleichzeitig mit den Daten arbeiten, ist es normalerweise die Ausnahme, daß Sie z.B. ohne weitere Vorkehrungen einfach einen PACK durchführen können.

Grundsätzlich gilt, daß nur die Daten gemeinsam zur Verfügung stehen. Alle Variablen, Arrays und SQL-Cursordateien sind nur auf dieser einen Station sichtbar. Es ist nicht möglich, daß eine andere Station Ihre Cursordatei überschreibt. Wichtig ist ebenfalls, daß jede Station einen eigenen Satzzeiger (Recordpointer) hat, ebenso wie jede Station die Indexsortierung unabhängig voneinander einstellen kann.

Dadurch, daß die Daten zentral an einem Ort (dem Server) gehalten werden und alle Anwender gleichzeitig Zugriff zu den Daten haben und z.B. neue Datensätze anlegen oder sie verändern, müssen Sie spezielle Vorsichtsmaßnahmen walten lassen, damit dies alles ohne Probleme abläuft.

Gottseidank sind die prinzipiellen Änderungen bei FoxPro nicht allzu kompliziert, denn:

Die grundsätzliche Änderung besteht eigentlich nur im Einfügen zweier Befehle in ihrem Startprogramm, noch bevor irgendeine Datei geöffnet wird.

SET EXCLUSIVE OFF
SET REPROCESS TO AUTOMATIC

Damit kommen Sie schon ganz gut über die Runden! Ab sofort wird jede Datei im SHARED Modus geöffnet, und FoxPro kümmert sich um die erforderlichen Sperren.

Klingt zu gut um wahr zu sein, nicht wahr? Nun ja, wenn's soo einfach wäre, müsste ich mir hier nicht die Finger platt tippen.... Also, schaun mer mal.

Datei- und Satzsperren

In FoxPro gibt es verschiedene Abstufungen in der Steuerung des Datenzugriffs:

SHARED:

Alle Daten sind allen im Netz lesend und schreibend zugänglich. Im Normalfall wird man also immer im SHARED Modus die Dateien öffnen.

RECORD LOCK:
Ein oder mehrere Datensätze sind gegen das Schreiben geschützt, können aber weiterhin gelesen werden. Typischer Anwendungsfall: Sobald ein Datensatz bearbeitet wird, wird dieser mit einer Satzsperre belegt, dadurch ist gewährleistet, daß nicht gleichzeitig zwei Stationen den selben Datensatz verändern.

FILE LOCK:
Eine gesamte Datei ist gegen das Schreiben gesperrt, kann aber weiterhin gelesen werden. Bei einigen Aktionen wird es nötig sein, die gesamte Datei mit Schreibschutz zu belegen: z.B. wenn Sie einen Monatsbericht o.ä. drucken; Sie würden in Teufels Küche kommen, wenn die Endsumme eines Berichtes nicht mit den Einzelposten übereinstimmen würde. Auch FoxPro selbst setzt automatisch einen File Lock bei bestimmten Befehlen (Siehe Aufstellung)

HEADER LOCK:
Wenn der Datei-Header gesperrt wird, können alle zwar auf die Datensätze lesend und schreibend weiterhin zugreifen, Neuanlagen anderer werden aber verhindert. Seit der VFP5 Version können wir auch selbst den Header sperren mittels Angabe der Satznummer 0 im RLOCK() Aufruf.

EXCLUSIVE:
Die Datei kann nur von einer Station geöffnet werden, alle anderen Stationen haben keinen Zugriff. Diesen Modus benötigen Sie meist bei generellen Wartungsarbeiten (Reorganisation usw.) FoxPro verlangt exklusiven Zugriff bei den Befehlen:

INDEX ON... INSERT (Nicht zu verwechseln mit INSERT INTO...)
MODI STRUC PACK
REINDEX ZAP

Falls zum Zeitpunkt des Befehls die Datei nicht exclusiv geöffnet war, müssen Sie sie schliessen und mit dem Exclusive Parameter neu öffnen.

Hier noch ein Tip fürs interaktive Arbeiten: Normalerweise hat man auch im interaktiven Betrieb den SHARED Modus aktiviert. Um die aktuelle Datei sofort wieder in den Exklusiv-Modus zu schalten, können Sie entweder langwierig den Pfad und Dateinamen eintippen, oder aber kurz und schmerzlos:

USE DBF() EXCL

Diese Tabelle zeigt, welche Sperren FoxPro automatisch bei bestimmten Befehlen setzt:

Befehl Sperrt:
ALTER TABLE
INSERT (xbase)
DELETE [ALL | REST | NEXT x]
REPLACE [ALL | REST | NEXT x]
UPDATE (SQL)
UPDATE (xBase)
FILE
FILE
FILE
FILE
FILE
FILE

APPEND
APPEND BLANK
APPEND FROM
INSERT (SQL)

HEADER
HEADER
HEADER
HEADER

APPEND MEMO
BLANK
DELETE [NEXT 1]
GATHER
MODI MEMO
READ
RECALL [NEXT 1]
UPDATE (SQL)

REPLACE [NEXT 1]

BROWSE
CHANGE
EDIT
DELETE RECORD x
REPLACE RECORD x

RECORD
RECORD
RECORD
RECORD
RECORD
RECORD
RECORD
RECORD und alle angegebenen Dateien in der FIELD Anweisung RECORD und alle angegebenen Dateien in der FIELD Anweisung

 

RECORD und alle relationierten Sätze

RECORD x
RECORD x

 

Bei "Header"-Sperren wird von FoxPro nur der Dateikopf für den Zeitraum des Anfügens einzelner Datensätze gesperrt (Im Header ist z.B. die Anzahl Sätze und das letzte Zugriffsdatum gespeichert). Dies ist Multiuser-freundlicher als eine komplette Dateisperre.

Wenn eine Datei auf einer Station im Exclusive Modus geöffnet ist, und Sie von einer anderen Station versuchen, ebenfalls diese Datei zu öffnen, belohnt sie FoxPro mit der Fehlermeldung #3 "Datei wird bereits benutzt". Falls Sie ein DOS Netzwerk einsetzen (alles, was SHARE benötigt) können Sie auch die Fehlermeldung #1705 "Zugriff auf Datei verweigert" erhalten. Sie sollten also Ihre Datei-Öffnungs Routinen dementsprechend erweitern. Ebenso müssen Sie in Betracht ziehen, daß Sie eine Datei nicht exclusiv öffnen können, weil eben noch ein anderer Anwender die Datei offen hat.

Prinzipiell müssen Sie durch eine geeignete LOOP Konstruktion solange probieren, bis Sie den Zugriff bekommen (oder der Anwender entnervt abbricht). Prinzipiell könnten Sie also so vorgehen:

lcOldError = ON("ERROR")
ON ERROR lnFehler = ERROR()
lnFehler = 0
DO WHILE .T.
USE (lcDatei)
DO CASE
CASE lnFehler = 0
EXIT
CASE INLIST(lnFehler, 3, 1705)
WAIT WINDOW "Datei zur Zeit in Benutzung!" TIMEOUT 1
CASE ....
&& andere Möglichkeiten
ENDCASE
ENDDO
ON ERROR &lcOldError

In VFP ist der richtige Weg dazu die Erstellung eines Objektes basierend auf der Klasse CUSTOM, das über seine ERROR Methode entsprechend agiert.

Haben Sie nun die Datei erfolgreich öffnen können, ist der weitere Ablauf zuerst einmal identisch zum normalen SingleUser Betrieb: BROWSE, SEEK, usw. funktionieren wie gehabt. Der große Unterschied kommt nun erst beim Verändern der Daten. Egal was sie mit einem Datensatz auch anstellen: Sie müssen den anderen Anwendern im Netz mitteilen, daß dieser Datensatz nun von Ihnen bearbeitet wird. Dies erfolgt normalerweise durch Setzen der Satzsperre mittels RLOCK(). Sobald Sie einen Datensatz erfolgreich gesperrt haben, können andere Anwender nur noch lesend auf ihn zugreifen. Nach Beendigung Ihrer Arbeiten wird der Satz mittels UNLOCK wieder freigegeben und ihre Änderungen den anderen Anwendern zur Verfügung gestellt.

Problematisch:
Ohne Satzsperren:
User A Zeit User B
Satz lesen Satz lesen
  Editieren
Editieren  
  Speichern
Speichern  

Bei diesem Beispiel lesen beide Anwender den selben Datensatz, und beschließen beide, eine Änderung zu machen. User B kann schneller tippen, ist daher früher fertig und speichert ab. User A überschreibt nun mit seinem Speichern alle Änderungen von User B. Sicherlich nicht Sinn eines Netzwerks...

Und so sollte es richtig ablaufen:

Mit Satzsperren:
User A Zeit User B
Satz lesen Satz lesen
  Satz gesperrt !
Satz sperren ? Editieren
Satz sperren ?  
Satz sperren ? Speichern
Satz gesperrt Entsperren
Satz lesen  
Editieren  
Speichern  
Entsperren  

Durch die Satzsperre muß User A solange warten, bis User B mit seinen Änderungen fertig ist. Erst nach Freigabe kann User A den Datensatz (mit den Änderungen!) nochmals neu einlesen, selbst sperren und bearbeiten.

Aber Bill Gates sei Dank: In VFP kann das eigenhändige Sperren / Entsperren in den meisten Fällen getrost FoxPro überlassen werden.

Achtung: Puffer-Falle!

Ein nicht unwesentlicher Teil von FoxPro's Geschwindigkeit beruht auf den ausgefeilten Datenpufferungs-Methoden. Beim Öffnen liest FoxPro nämlich einen Teil der Daten schon vorweg in seinen Speicher. Alle weiteren Zugriffe auf diese Datensätze erfolgen dann auf diesen Zwischenspeicher. Bei einem Einzelplatzrechner haben Sie damit auch keinerlei Probleme. Im Netzwerkbetrieb kann allerdings folgendes auftreten:

User A und User B stehen (wieder einmal) auf dem selben Datensatz. User A sperrt den Datensatz. Beide kopieren sich den Datensatz in ein Array (z.B. mit dem SCATTER Befehl). User A editiert, speichert seine Änderungen ab und entlässt den Datensatz aus seiner Sperre. User B macht ein erneutes SCATTER, um die Änderungen von User A zu erhalten, aber.... er bekommt die selben Inhalte wie bei dem Scatter vorher! Soviel Sie auch SCATTERn, sie sehen die geänderten Daten nicht. Ursache ist eben jener Puffer: Alle Lese und Schreibaktionen werden auf den Puffer ausgeführt und nicht gegen die "echten" Daten im Netzwerk. Um diesen Effekt auszubremsen, müssen Sie FoxPro dazu bringen, den Puffer neu einzulesen:

Die beste Methode ist zweifellos die dritte Option: Grundregel aller Netzwerkanwendungen: Willst du saubere Daten haben, dann mußt du diese sperren. Unser obiges Beispiel können wir also zum ordnungsgemäßen Laufen bringen, indem wir vor dem zweiten SCATTER ein einfaches RLOCK anbringen.

Nun ist es aber in manchen Anwendungen unerwünscht, den Datensatz nur aus Gründen des Puffer-Auffrischens zu sperren. Dann hilft am besten Methode zwei: Ein seltsam anmutendes GOTO RECNO() erledigt dies elegant. FoxPro tritt sozusagen auf der Stelle, aber der Puffer hat den neuesten Wert.

Die SET REFRESH Option ist, da sie zu einem ständigen Neueinlesen alle <x> Sekunden führt, ein Netzwerk-Performance Killer. Stellen Sie sich 20 oder 50 Stationen vor, die allesamt z.B. jede 5 sec. ihren Puffer erneuern, sprich: die nächsten 10 oder 20 Datensätze frisch einlesen. Daher sollte die SET REFRESH Einstellung nur dann ungleich 0 gesetzt, wenn sie wirklich benötigt wird. (Mehr dazu noch beim Thema BROWSE)

Neues Handling in den Visual FoxPro Versionen

Zu behaupten, daß VFP´s Netzwerkfähigkeiten wesentlich erweitert wurden, wäre eine grobe Fehleinschätzung. Microsoft hat dermaßen viele Änderungen und Wünsche eingebaut, daß nichts mehr so funktioniert, wie man es von den alten FoxPro Versionen her gewohnt war. Dies aber im positiven Sinne, denn mit VFP3/5 haben wir nun sehr ausgefeilte Möglichkeiten, auf jedwede Multiuser-Problematik einzugehen.

Die eigentliche Grundlage für die wesentlich verbesserten Techniken sind das Data Buffering und die Private Datasessions.

Data Buffering führt dazu, daß wir uns nicht mehr mit SCATTER/GATHER rumärgern müssen und auch das selbstkontrollierte LOCK()/UNLOCK können wir im Normalfall vergessen. Vieles, was unter 2.x noch sehr schwierig oder garnicht zu realisieren war, ist nun in wenigen Minuten erledigt, ohne auch nur ein RLOCK zu verwenden. Vorbei sind die Zeiten, wo man mehrere Arrays angelegt hat, und diese ständig auf Änderungen überprüft hat, ganze fünf Funktionen wickeln nun alles ab.

Durch Private Datasessions können wir ohne Bedenken beliebige Masken parallel laufen lassen, ohne daß sich die Datenbestände überschreiben. Man kann sich die P.D.S (nein, nicht was Sie jetzt wieder denken...) als Abkapselung der Daten innerhalb einer Form vorstellen. Jede Form ist dadurch wie ein eigenständiger Netzwerkanwender zu sehen. Sie können ohne Probleme dieselbe Form mehrmals starten, und parallel verschiedene oder auch den selben Datensatz bearbeiten.

Data Buffering

Dies ist ein absolut neues Feature in VFP, das uns die meiste Arbeit mit Zwischenspeichern von Datensätzen abnimmt. Zwar kennen alte Hasen ähnliches schon als "AUTOMEM" von ARAGO , aber VFP´s Implementation geht noch wesentlich weiter. Zwar ist die grundlegende Methodik noch immer dieselbe, aber sie müssen sich um wesentlich weniger kümmern. Vieles was wir bisher per Hand codierten, wird nun von der Engine intern wesentlich sicherer erledigt.

Die alte SCATTER/GATHER Editierung bzw das Umkopieren auf Speichervariablen macht unter VFP keinen Sinn mehr, da VFP bei einer Variable keinen Bezug zu dem darunterliegenden Datenbankfeld mehr herstellen kann, und folglich alle unsere so schön definierten VALID und DEFAULT Funktionen nutzlos blieben. Folglich bleibt uns nur das direkte Feldeditieren, und hier kommt nun der Buffer zum Zuge.

Wenn wir hier von einem Buffer reden, dann ist damit ein spezieller Datenbereich in VFP gemeint. Wenn wir eine Tabelle öffnen, so liest VFP die ersten paar Datensätze in seinen internen Buffer ein. Wenn danach mit einem GET/READ bzw einer Textbox der Datensatz direkt bearbeitet wird, wurden die Änderungen früher sofort an die Platte weitergegeben.

Nun haben wir auch noch die Möglichkeit, den Transfer zwischen Platte und Buffer zu kontrollieren, indem wir den Buffermodus aktivieren. Wir können uns den Buffermodus auch als internes SCATTER vorstellen, das trotzdem wie ein Feld angesprochen wird. Falls wir das Tablebuffering aktiviert haben, hält VFP für jeden geänderten Record ein SCATTER, und selbst Neuanlagen und Löschungen werden im Speicher gehalten, bis Sie den Befehl zum wegschreiben geben. Dieses Wegschreiben erreichen wir nun durch zwei neue Funktionen: TableUpdate() und TableRevert(), die nun die Aufgabe von SCATTER / GATHER übernehmen.

Eingestellt wird der Buffermodus entweder über das entsprechende Property innerhalb einer Form (BufferMode), DataEnvironment (BufferModeOverwrite), oder über die Funktion CursorSetProp()..

= CursorSetProp("Buffering", 1) && kein Buffering

"Cursor" bedeutet übrigens in diesem Zusammenhang schlicht CURrent Set Of Records, also der aktuell gewählte, bzw. sichtbare Datenbestand; nicht zu verwecheln mit dem ebenfalls verwendeten Begriff Cursor für eine virtuelle dbf aus einer Abfrage

Bei allen Buffermodi > 0 muß MULTILOCK auf ON gesetzt werden, ansonsten erhalten Sie eine Fehlermeldung.

Der Buffermodus ist für alle eingebundenen Tabellen als auch für freie und 2.x kompatible Tabellen anwendbar.

Buffer Modi

Es gibt verschiedene Möglichkeiten zu puffern: Auf Satzebene (Row) und auf Tabellenebene (Table). Bei beiden Methoden gibt es zusätzlich noch zwei verschiedene Methoden zu sperren: pessimistisch (bei Beginn der Eingabe) oder optimistisch (beim Speichern).

Row Buffering

Sobald dieser Modus aktiviert ist, werden alle Änderungen im Speicher gehalten. Mittels TABLEUPDATE() oder TABLEREVERT() können die Änderungen gespeichert bzw. verworfen werden. Es kann immer nur ein Satz gepuffert werden, sobald Sie den Satzzeiger bewegen und Änderungen anstehen, wird automatisch ein TABLEUPDATE erzwungen. Falls dabei Fehler auftreten, bleibt der Satzzeiger auf dem aktuellen Satz stehen.

Pessimistisches Row Buffering

Bei dieser Sperrmethode wird automatisch eine Satzsperre ausgelöst, sobald ein Feld verändert wird. Falls der Sperrversuch fehlschlägt wird der Buffer zurückgesetzt, ansonsten werden die Änderungen im Speicher durchgeführt. Die Satzsperre wird dabei solange gehalten bis eine der folgenden Aktionen stattfindet:

Wenn beim Verändern des Satzzeigers das Updaten mißlingt, bleibt der Buffer erhalten und der Satz weiterhin gesperrt. Ähnlich ist es beim Schliessen der Datei: Wenn die implizit aufgerufene Funktion TABLEUPDATE einen Fehler meldet, kann die Datei nicht geschlossen werden.

Aufgrund der pessimistischen Sperre sollte ein TableUpdate eigentlich nie einen Fehler durch Änderungen anderer Anwender bringen. Es können aber Fehler entstehen durch:

Optimistisches Row Buffering

Mit aktivierter optimistischer Sperrung wird der Datensatz erst dann gesperrt, wenn VFP versucht, den Buffer wegzuschreiben. Dabei wird von VFP automatisch überprüft, ob sich der Datensatz im Netz zwischenzeitlich geändert hat. Falls nicht, wird der Lock aufgehoben. Der Schreibvorgang wird durch die selben Ereignisse wie oben ausgelöst. Zusätzlich zu den obigen Fehlermöglichkeiten kommen hier aber noch hinzu:

Table Buffering

Wenn der Tabellen-Buffermodus aktiviert ist, werden alle Änderungen an allen Datensätzen zwischengepuffert. Auch (APPEND BLANK oder INSERT). Da mehrere Datensätze gepuffert werden, führt ein Satzwechsel NICHT zu einem automatischen TableUpdate. Der Begriff Table-Buffering ist etwas irreführend. Natürlich wird nicht die gesamte Tabelle im Buffer gehalten, sondern nur die geänderten Datensätze. Falls Sie in diesem Modus unbedingt ein REPLACE ALL auf eine Millionen Satz Datei machen wollen, sollten Sie vorher für genügend freien Plattenplatz in ihrem Temp-Verzeichnis sorgen.

Wenn Sie im Table-Buffer Modus neue Datensätze erzeugen, erhalten diese NEGATIVE Satznummern. D.h. Erste Neuanlage hat RECNO() = -1, der nächste dann -2 usw. Auf diese Weise können Sie sehr schnell Neue von existierenden Datensätzen unterscheiden. Erst wenn die Datensätze erfolgreich weggeschrieben wurden, erhalten sie ihre entgültige Satznummer.

Pessimistisches Table Buffering

Hier werden alle Datensätze automatisch gesperrt, sobald an ihnen Änderungen vorgenommen werden. Die Sperren bleiben auch beim Satzwechsel bestehen.

Die Satzsperren werden unter den gleichen Bedingungen wie beim Satzweisen Sperren aufgehoben.

Implizite TABLEUPDATE Aufrufe beinhalten immer den Parameter "Alle Sätze schreiben". Wenn hierbei ein Satz nicht erfolgreich weggeschrieben werden kann, bleiben alle Satzsperren weiterhin aktiv.

Optimistisches Table Buffering

In diesem Modus werden Satzsperren erst dann gesetzt, wenn VFP alle Änderungen wegschreiben will. Falls dabei ein Satz nicht aktualisiert werden kann, wird der gesamte Updatevorgang abgebrochen, wobei alle Recordlocks stehen bleiben. Die Datensätze sind dann meist schon teilweise geschrieben und teilweise ungeschrieben. Hier kann mit einer Transaktion das Verfahren verbessert werden (siehe auch Tips am Ende).

Buffering, Valids & Trigger

Feld- und Satz Regeln werden wie gewohnt abgearbeitet, auch wenn der Buffermodus aktiv ist. Einzige Ausnahme ist der APPEND BLANK Befehl: Hier werden die Abprüfungen erst beim tatsächlichen physikalischen Anhängen an die Tabelle durchgeführt. Dies ist übrigens die einzige Möglichkeit, einen Datensatz mit leeren Feldern zu erstellen, bei dem die Regeln nur "nichtleere" Felder erwarten.

Die Trigger verhalten sich ebenfalls wie gewohnt: Sie werden gestartet, wenn der eigentliche Update/Delete/Insert Fall auftritt. Dies ist beim Table-Buffering erst dann, wenn alle geänderten Sätze tatsächlich weggeschrieben werden, und nicht wenn der APPEND BLANK Befehl abgesetzt wird.

Genauso werden die Primary und Candidate Key Überprüfungen erst vorgenommen, wenn die Sätze weggeschrieben werden.

Die Reihenfolge der Überprüfungen läuft folgendermaßen ab:

  1. Datenfeld Valid
  2. Textbox Valid
  3. Satz Valid
  4. Primary/Candiate Eindeutigkeitstest
  5. Trigger

Tabellen- oder Satzweise puffern ?

Aufgrund der Tatsache, daß man mit Table-Buffering das Row-Buffering hervorragend nachahmen kann, und des einfacheren Verhaltens beim APPEND BLANK ist es empfehlenswert, nur mit TableBuffering zu arbeiten. Auf diese Weise ist man für alle Eventualitäten gewappnet. Auch ist es auf Dauer einfacher, überall nach dem selben Modus zu arbeiten.

Der einzige Bereich, wo Table-Buffering nicht so empfehlenswert ist, sind direkte Eingaben in BROWSE-Felder. Nachdem man das in Programmen aber sowieso nicht macht, ist´s also kein Problem.

Pessimistisch oder optimistisch ?

Nachdem die beiden Verfahren in VFP prinzipiell dasselbe machen, was alle xBase Programmierer seit Jahren machen, bleibt die alte Glaubensfrage weiterhin bestehen. Beide Methoden haben ihre Vor- und Nachteile, daher müssen Sie selbst entscheiden, welches Verfahren für Ihren Fall das geeignetere ist. Nachfolgend ein paar Überlegungen dazu:

Schreiben und Verwerfen des Puffers

Das Wegschreiben gepufferter Daten erfolgt mit der TABLEUPDATE Funktion, die im Prinzip dem GATHER MEMVAR entspricht. Das Verwerfen des Buffers wird mit TABLEREVERT erzielt, was einem erneuten SCATTER MEMVAR entsprechen würde.

TableUpdate(lAllRows | nAllRows, lForce, cAlias, aError)

lAllRows / nAllRows

Der erste Parameter ist nur im Tablebuffer Modus wichtig. Hier kann entweder nur der aktuelle Satz oder alle geänderten Sätze weggeschrieben werden. In VFP5 kann der erste Parameter nun nicht nur logisch, sondern auch numerisch sein.

Hierbei entpricht der Wert 0 dem selben Verhalten wie .F., was bedeutet: Nur den aktuellen Satz schreiben.

Ein .T. bzw 1 bedingt das Schreiben aller geänderter Sätze. Im Konfliktfall wird mit einer Fehlermeldung abgebrochen.

Eine 2 arbeitet wie 1, aber im Konfliktfall wird keine Fehlermeldung ausgeben, sondern weitergemacht. Datensätze, die dabei nicht geändert werden konnten, können optional automatisch in einem Array abgelegt werden (siehe 4.Parameter)

lForce

Der zweite Parameter ist nur im optimistischen Modus wichtig: Wenn gesetzt, werden alle Änderungen brutal weggeschrieben, ohne Rücksicht auf eventuell schon erfolgte andere Änderungen.

aError

Wird nur benötigt, wenn der erste Parameter auf 2 steht. Falls dieser Parameter angegeben wird, dann erstellt VFP5 automatisch ein Fehlerarray, das die Satznummern mit den Konflikt-Records enthält.

Die Funktion gibt einen logischen Wert zurück, der über den Erfolg Auskunft gibt. Probleme können bei folgendem Scenario auftreten: Tablebuffering aktiviert, mehrere Sätze geändert, ein anderer als der erste Satz kann nicht erfolgreich weggeschrieben werden. Unter diesem Umstand wird der komplette Updatevorgang abgebrochen, mit einigen schon geschriebenen Datensätzen und einigen ungeschriebenen. Abhilfe schafft hier die Verwendung einer Transaktion (siehe Beispiel dort).

Falls man auf gepufferte lokale Updateable Views schreibt, die sich wiederum auf gepufferte lokale Tabellen beziehen, muß man in einem Zwei-Schritt Verfahren vorgehen:

  1. Zuerst ein TableUpdate auf die View. Dadurch werden die Änderungen der View in den Puffer der Tabelle geschrieben.
  2. Danach ein TableUpdate auf die Tabelle, um deren Puffer wegzuschreiben.

Daraus folgt, daß man in so einem Fall nur die View puffern sollte....

TableRevert(lAllRows [,cAlias])

Diese Funktion gibt einen numerischen Wert zurück, der aussagt, wieviele Datensätze erfolgreich zurückgenommen werden konnten. Im Normalfall kann man davon ausgehen, daß hier immer erfolgreich abgeschlossen werden kann. Bei allen meinen Tests konnte ich nie eine Diskrepanz feststellen. Einziger Verwendungszweck des Rückgabewertes wäre

x = TABLEREVERT()
= MESSAGEBOX(STR(x)+" Datensatz-Änderungen storniert")

Das Verwenden von TableRevert auf ungebufferten Tabellen führt zu einem Fehler. Den Buffermodus kann man über GETCURSORPROP("Buffering") abprüfen.

Update Konflikte

Trotz aller noch so ausgefeilten Buffermethoden bleibt es uns nicht erspart, sich über Zugriffskonflikte Gedanken zu machen, denn diese Problematik wird immer bleiben. Aber im Gegensatz zu früher haben wir nun ausgezeichnete Möglichkeiten, hier einzugreifen.

GetFldState(nFieldNumber | cFieldName)

Diese Funktion gibt uns alle Informationen über den Zustand eines kompletten Datensatzes oder eines Feldes: geändert, gelöscht, neu angelegt, wieder entlöscht usw. Durch Angabe der Feldnummer oder -1 für alle Felder erhalten wir eine String mit Statusflags zurück.

? GETFLDSTATE(-1) "1112111"

Die erste Stelle gibt Auskunft über den Löschzustand, ab Stelle 2 sind die Feldflags. Eine 1 bedeutet "Feld unverändert", eine 2 sagt aus, daß dieses Feld verändert wurde. Über die Konstruktion

IF "2" $ GETFLDSTATE(-1)

kann man sehr einfach feststellen, ob man überhaupt Daten wegschreiben muß.

Achtung: Nur nach Verlassen des Feldes korrekt!

Es gibt einen Problemfall, der Ihnen graue Haare bringen kann: GetFldState (und auch das Eingabefeld selbst) werden nur dann geupdated, wenn man das Feld verlässt. Die Funktion ist nicht dafür gedacht, Ihnen Feldinformationen zu geben, während Sie noch in dem betreffenden Feld sind. Erst durch das Verlassen mittels TAB, ENTER, Pfeiltasten bzw Click auf ein anderes Objekt wird der geänderte Status bemerkt. Folgendes Scenario funkioniert aber leider nicht:

  1. Datensatz wird angezeigt, Anwender beginnt zu tippen.
  2. Noch während er im Feld ist, wählt er eine Menüoption an, bzw clickt einen Toolbar-Button an.
  3. Da unter diesen Umständen das Feld noch nicht verlassen wird, kann folglich die GETFLDSTATE Funktion noch nicht über den geänderten Zustand berichten.

Daraus folgt, daß eine Änderung in einem Feld nicht bemerkt wird, wenn der Anwender dieses Feld nicht verlässt und die Speichern-Option über Menü/Toolbar angewählt wird. Es gibt verschiedene Methoden, mit diesem Problem umzugehen, siehe dazu die Tips und Tricks im Anhang.

GetNextModified()

Diese nette Funktion ist nur im TableBuffer Modus sinnvoll. Sie gibt die Satznummer des nächsten, geänderten Records zurück. Dabei werden geänderte Felder, Neuanlagen und Sätze an denen sich der Löschstatus geändert hat, erkannt.

Problem: Nur nach Verlassen des Feldes korrekt

Auch hier tritt das selbe Problem auf, wie bei GetFldState. Ist auch logisch, denn woher soll GetNextModified den Satz finden, wenn GetFldState ihn schon nicht erkennt...

Problem: GetNextMod bewegt den Satzzeiger

Dieses Phänomen ist schon etwas diffiziler. Die Funktion gehört zu den wenigen Funktionen, die beim Beenden zwar wieder auf dem Datensatz stehen, mit dem sie angefangen hatten, zwischenzeitlich aber den Satzzeiger bewegen. (Eine andere ist z.B. KEYMATCH()). Dies kann nun zu Problemen führen, wenn dabei das Satz-Valid oder ein Feld-Valid einen Fehler meldet. Dadurch bekommen Sie Fehlermeldungen an Stellen, wo sie garnicht damit rechnen.

CurVal(), Oldval()

Mit CURVAL() erhalten Sie für das angegebene Feld den aktuellen Wert draussen im Netz. Passend dazu gibt OLDVAL() den Wert zurück, bevor Sie mit den Änderungen anfingen. Also analog zu den ARRAY-SCATTER Methoden aus den alten Tagen. Durch einen einfachen Vergleich der beiden Werte können Sie feststellen, ob sich ein Datensatz zwischenzeitlich geändert hat.

OLDVAL() ist auch sehr gut geeignet, um eine feldbezogene UNDO Funktionalität zu implementieren.

Bei pessimistschem Locking haben CURVAL und OLDVAL immer den selben Wert, da es ja durch die Sperre nicht möglich ist, daß sich der Satz draußen ändert.

Transaktionen

Diese drei einfachen Befehle:

BEGIN TRANSACTION
END TRANSACTION
ROLLBACK

sind alles, um eine enorme Datensicherheit mit minimalen Aufwand zu erreichen. Durch Einschliessen Ihres Updatecodes mit einer Transaktion können sie kiloweise Code einsparen, der sowieso nie sicher laufen würde... z.B können Sie:

Transaktionen können bis zu fünfmal ineinander gestaffelt sein; die Funktion TXNLEVEL() sagt Ihnen, wie tief sie sich schon verstrickt haben.

Problem: Transaktionen mit freien Tabellen

Ab dem Befehl BEGIN TRAN werden alle Änderungen an allen Tabellen mitprotokolliert, sofern diese ein Teil eines DBC´s sind. D.h. Freie Tabellen können nicht geschützt werden! Änderungen an freien Tabellen werden also gnadenlos weggeschrieben.

Eingabe-Formulare

In VFP werden Eingabeobjekte (Textbox, Editfeld usw) nun an die Daten "gebunden". Dies wird mit dem Property CONTROLSOURCE erreicht. D.h. Die Felder schreiben nicht mehr auf Speichervariablen, sondern direkt auf die Tabelle. Was früher ein No-No war, soll nun richtig sein? Ohne Buffermodus wird tatsächlich direkt in die Datei editiert, genauso wie unter 2.6. Aber sobald wir den Buffermodus einschalten, ist alles ok.

Buffermode Property

Dieses Property einer Form kann entweder auf Pessimistisch oder Optimistisch gesetzt werden. Sie können es auch auf None stellen, aber dann müssen Sie selbst im Code die Einstellungen setzen. Berachten Sie dieses Property als Komfort für faule Programmierer, denn VFP führt nun ein implizites CURSORSETPROP() aus, wenn es die Tabellen im DataEnvironment öffnet. Dabei werden Tabellen und Views, die als RecordSource für Grids dienen, mit TableBuffereing geöffnet; alle anderen Tabllen werden mit RowBuffering geöffnet; und Views prinzipiell mit Optimistic RowBuffering.

Die Datenumgebung

Alle Forms haben ein DataEnvironment (Pseudo-) Objekt, egal ob Sie es verwenden oder nicht. (Datenumgebung). Alle Tabellen/Views, die Sie hier angeben, werden automatisch geöffnet, wenn die Form anstartet. Dabei werden auch die Relationen gesetzt, die Sie aber auch löschen bzw selbst setzen können.

Diese CURSOR-"Objekte" im DataEnvironment haben ebenfalls Properties, Events und Methoden, von denen die meisten am besten auf ihren Defaultwerten gelassen sollten. Eigentlich sind diese Einstellungen nicht anderes als eine OOP-Version der alten SET... Befehle. So ist das ORDER Property nichts anderes als eine Anweisung zu einem SET ORDER TO ... Befehl.

Falls Sie also das DataEnvironment nicht verwenden wollen, passiert nicht viel. Sie können alles auch durch gewohnten xBase-Code ebenfalls erreichen. Da das DataEnvirnonment hartcodierte Pfadangaben zu den verwendeten DBCs und Tabellen hinterlegt, macht es unter Umständen Sinn, die Tabellen per Hand (im LOAD-Event) zu öffnen.

BuffermodeOverride Property

Dies ist das interessanteste Property des ganzen Cursor-Objektes. Damit kann für jede einzelne Tabelle ein CURSORSETPROP("Buffering") durchgeführt werden. Somit sind sie nicht abhängig von der BufferMode Einstellung der Form, sondern können gezielt einzelne Tabellen anders buffern.

Z.B. Könnten Sie die Rechnungskopf Tabelle pessimistisch sperren, die Rechnungszeilen aber optimistisch verwenden.

Wenn Sie das DataEnvironment nicht verwenden, dann müssen Sie die Befehle wiederum per Hand codieren, dies geschieht normalerweise im FORM-LOAD.

Datasession Property

Damit können Sie einstellen, ob VFP einen abgekapselten Datenbereich für diese Form anlegt. Ich empfehle Ihnen, hier immer Option 2 (Private Datasession) einzustellen, wenn Sie Datengebundene Objekte in der Form haben.

Durch diese einfache Einstellung wird erreicht, was in FP2.x nur mit Unmengen von Code extrem aufwendig zu erreichen war. Das unabhängige Laufenlassen meherer Forms nebeneinander ist nun nur noch ein simples Aktivieren. VFP kümmert sich um Satzzeiger, Index Tags, Relationen, Arbeitsbereiche, usw für jede einzelne Form.

VFP erreicht dies durch ein internes USE AGAIN auf die verwendeten Tabellen/Views. Dies ist sogar ziemlich ähnlich wie man es früher in FPW gemacht hätte, mit dem Unterschied, daß VFP sogar die eindeutigen ALIASe usw für unsere Tabellen verwaltet. Sie erstellen einmal eine Maske und rufen Sie auf, sooft sie wollen. Sie haben sicher schon gelesen, daß VFP nun 32767 Selectebenen hat, aber durch das implizite USE AGAIN sind das 32767 Selectebenen pro Datasession!

DataSessions werden im Umgebungsfenster ebenfalls dargestellt, dazu bekam das Umgebungsfenster quasi eine dritte Dimension dazu. In der DropDown Listbox oben können Sie nun aus den verschiedenen Datasessions auswählen, und eine nach vorne bringen. Datasession 1 ist die sog. Default-Datasession, welche im Befehlsfenster aktiv ist (Dies ist identisch zu FPW, wo wir ja nur eine Datasession hatten). Jeder Aufruf einer Form mit Datasession=2 führt zu einer neuen Private Datasession, die dabei fortlaufend nummeriert werden. Dieser Integerwert ist im DataSessionID Property der Form wiederzufinden.

Obwohl es daher machbar wäre, die DataSessionId gleich zu setzen mit der einer anderen Form: tun sie´s nicht! Sobald sie den Wert ändern, verlieren Sie den Link der Eingabefelder zu den Daten, und das kann auch nicht mehr aktiviert werden.

Manuelles Verwalten der Tabellen

Sobald Sie eine Form mit Private DataSession aktivieren, werden alle Tabellenaktionen (Öffnen/Schliessen Order usw) unabhängig vom Rest der Applikation durchgeführt. Dieser Code wird normalerweise im LOAD bzw UNLOAD Eevent eingebaut.

Sogar SQL generierte Cursors und CREATE CURSOR basierende Dateien werden sauber den jeweiligen Sessions zugeordnet. Sogar das mehrfache Aufrufen derselben Form führt dazu, daß VFP die internen Namen dieser Cursordateien sauber verwaltet, sie müssen sich also keine großartigen Namenskonventionen ausdenken. Wo Sie aber drandenken müssen, ist das manuelle Schließen ihrer Dateien am Ende.

Default SET Einstellungen

Dadurch, daß Datasessions eine abgekapselte Welt für sich darstellen, werden viele SET Einstellungen wieder auf den VFP-internen Defaultwert zurückgesetzt, egal wie sie diese im Hauptprogramm eingestellt hatten. Folgende Einstellungen müssen daher in jeder neuen Datasession erneut eingestellt werden: r>
SET ANSI SET AUTOSAVE SET BLOCKSIZE SET CARRY
SET CENTURY SET COLLATE SET CONFIRM SET CURRENCY
SET DATABASE SET DATE SET DECIMALS SET DELETED
SET DELIMITERS SET EXACT SET EXCLUSIVE SET FIELDS
SET FIXED SET HOURS SET LOCK SET MARK TO
SET MEMOWIDTH SET MULTILOCKS SET NEAR SET NULL
SET POINT SET REPROCESS SET SAFETY SET SECONDS
SET SEPARATOR SET SYSFORMATS SET TALK SET UNIQUE

Übrigens ist in der VFP3 Online-Hilfe auch noch SET PATH mit aufgeführt, dies stimmt aber nicht.

Das Einstellen dieser SET Werte geht am besten im Form LOAD oder im DataEnvironment BeforeOpenTable Event. Wenn Sie eine Standard-Klasse für Ihre Forms verwenden, können sie das dort einmal ins LOAD reinsetzen und vergessen; bzw ganz elegant sich ein Environment-Objekt erstellen.

Wichtige Netzwerkbefehle:

Die nachfolgende Auflistung erklärt die im Netzwerk verwendeten FoxPro Befehle.

SET EXCLUSIVE [ON|OFF]

Mit SET EXCLUSIVE wird festgelegt, ob Tabellen in einem Netzwerk für alleinigen oder gemeinsamen Zugriff von Benutzern zur Verfügung stehen.

Die Änderung der Einstellung von EXCLUSIVE ändert den Status von vorher geöffneten Tabellen nicht. Wenn z.B. eine Tabelle mit SET EXCLUSIVE ON geöffnet wurde und später SET EXCLUSIVE auf OFF gesetzt wird, bleibt die Tabelle weiterhin nur für alleinigen Zugriff geöffnet.

Der Standard für SET EXCLUSIVE ist ON.

ON

Eine in einem Netzwerk geöffnete Tabelle kann bei SET EXCLUSIVE ON auch nur von dem Benutzer verwendet werden, der die Tabelle geöffnet hat. Andere Netzwerkbenutzer können auf diese Tabelle nicht zugreifen. Im Gegensatz zu FLOCK() verhindert SET EXCLUSIVE ON auch den Nur-Lese-Zugriff für alle anderen Benutzer. Eine Datei im Netzwerk kann auch durch Ausgabe von USE EXCLUSIVE für alleinigen Zugriff geöffnet werden. Satz- und Dateisperren sind bei einer exklusiv geöffneten Tabelle nicht erforderlich.

Durch Öffnen einer Datei für alleinigen Zugriff wird sichergestellt, daß die Datei nicht von anderen Benutzern geändert werden kann. Bei einigen Befehlen ist es erforderlich, daß eine Datei für exklusiven Zugriff geöffnet wird, bevor diese ausgeführt werden können. Hierbei handelt es sich um die Befehle ALTER TABLE, INSERT, INSERT BLANK, MODIFY STRUCTURE, PACK, REINDEX und ZAP.

OFF

Wenn eine Tabelle im Netzwerk mit SET EXCLUSIVE OFF geöffnet wird, können alle Netzwerkbenutzer auf diese zugreifen und sie ändern.

SET REFRESH TO

<AusdrN1>, <AusdrN2>Anzeige von Änderungen bei Sätzen, die von anderen Benutzern im Netz vorgenommen wurden.

Tabellen können im Netz zur gemeinsamen Benutzung geöffnet werden. So können Sätze, die Sie sich gerade ansehen, gleichzeitig von einem anderen Benutzer im Netz bearbeitet werden. Benutzen Sie SET REFRESH, um festzulegen, ob geöffnete Datenblattfenster mit von anderen Benutzern vorgenommenen Änderungen aktualisiert werden sollen oder nicht.

SET REFRESH beeinflußt die Anzeige von Sätzen in einem Datenblattfenster, das mit BROWSE, CHANGE oder EDIT geöffnet wurde. Memofelder, die zur Bearbeitung in einem Datenblattfenster geöffnet sind, werden ebenfalls aktualisiert.

Mit SET REFRESH kann auch angegeben werden, wie oft lokal zwischengespeicherte Daten aktualisiert werden.

Der Standardwert ist 0, 5

<AusdrN1>

Der numerische Ausdruck <AusdrN1> gibt an, wie oft ein Datenblatt- oder Memoeditierfenster aktualisiert wird. <AusdrN1> ist die Anzahl der Sekunden zwischen Aktualisierungen und kann ein Wert zwischen 0 und 3600 sein. Der Standardwert ist 0 Sekunden. Wenn <AusdrN1> ein Wert ungleich 0 ist und andere Benutzer die Sätze, die Sie sich ansehen, ändern, werden die Sätze nach Ablauf des Aktualisierungsintervalls aktualisiert. Ist <AusdrN1> 0, werden die von Ihnen angesehenen Sätze nicht aktualisiert.

FoxPro versucht, einen Satz zu sperren, wenn Sie diesen im Datenblattfenster ändern wollen. Wenn Sie versuchen, einen Satz zu sperren, der bereits von einem anderen Benutzer im Netz gesperrt wurde, erhalten Sie den aktualisierten Satz, wenn der andere Benutzer die Satzsperre aufhebt. Dies ist unabhängig von der Einstellung von SET REFRESH. Die Meldung "Satz wurde geändert." kann ebenfalls angezeigt werden.

<AusdrN2>

Die Netzwerkversionen von FoxPro speichern Teile der Tabellen im Arbeitsspeicher auf Ihrem Arbeitsplatzrechner. <AusdrN2> gibt an, wie oft diese lokalen Datenpuffer mit den aktuellen Daten vom Netzwerk aktualisiert werden. <AusdrN2> ist die Anzahl der Sekunden zwischen den Datenpufferaktualisierungen. Der Standardwert von <AusdrN2> ist 5. Sie können einen Wert zwischen 0 und 3600 angeben. Die Puffer werden nie automatisch aktualisiert, wenn <AusdrN2> 0 ist. Durch RLOCK() und Recordbewegungen (GO / SKIP) werden die aktuellen Werte vom Netz gelesen.

Wenn Sie für <AusdrN1> einen anderen Wert als 0 angeben, <AusdrN2> aber nicht angeben, wird <AusdrN2> auf denselben Wert gesetzt wie <AusdrN1>. <AusdrN2> wird jedoch auf 5 gesetzt, wenn Sie für <AusdrN1> 0 angeben und <AusdrN2> auslassen.

Die Leistung kann verbessert werden, wenn der Wert von <AusdrN2> erhöht wird.

SET REPROCESS TO <AusdrN> [SECONDS]

Bestimmen, wie oft oder wie lange FoxPro nach einem erfolglosen Sperrversuch erneut versucht, eine Datei oder einen Satz zu sperren.

Eine Satz- oder Dateisperre ist nicht immer beim ersten Versuch erfolgreich. Häufig wurde ein Satz oder eine Datei von einem anderen Benutzer im Netzwerk gesperrt. Ist eine erste Satz- oder Dateisperre nicht erfolgreich, bestimmt SET REPROCESS, ob zusätzliche Sperrversuche an der Datei/am Satz ausgeführt werden.

Mit SET REPROCESS können Sie die Anzahl der zusätzlichen Sperrversuche oder die Dauer dieser Versuche angeben. Die Angabe einer ON ERROR-Routine beeinflußt die Reaktion auf einen erfolglosen Sperrversuch.

TO <AusdrN> [SECONDS]

Der numerische Ausdruck <AusdrN> gibt an, wie oft FoxPro nach einem erfolglosen Versuch einen erneuten Satzsperrversuch ausführt.

Der Standardwert ist 0.

0

Wenn <AusdrN> 0 (der Standardwert) ist und ein Befehl oder eine Funktion zum Sperren eines Satzes/einer Datei abgesetzt wird, versucht FoxPro, den Satz/die Datei auf unbestimmte Zeit zu sperren. Die Systemmeldung "Versuche zu sperren... mit ESC abbrechen" wird angezeigt, während FoxPro einen Satz- oder Dateisperrversuch ausführt. Wird der Satz bzw. die Datei zur Sperrung freigegeben, während Sie warten, erfolgt die Sperrung, und die Systemmeldung wird gelöscht. Führt eine Funktion die Sperre aus, wird wahr (.T.) zurückgegeben.

Bei Betätigung von ESC als Antwort auf die Systemmeldung wird eine entsprechende Warnmeldung angezeigt (z.B. "Satz wird bereits von einem anderen Benutzer benutzt"). Führt eine Funktion die Sperre aus, wird keine Warnmeldung angezeigt und falsch (.F.) zurückgegeben.

Wenn eine ON ERROR-Routine angegeben wird und ein Befehl versucht, den Satz bzw. die Datei zu sperren, hat die ON ERROR-Routine Vorrang vor zusätzlichen Sperrversuchen. Die ON ERROR-Routine wird sofort ausgeführt. Es werden keine zusätzlichen Sperrversuche ausgeführt, und die Systemmeldung wird nicht angezeigt.

Wenn der Sperrversuch mit einer Funktion erfolgt, wird die Systemmeldung nicht angezeigt, eine ON ERROR-Routine wird nicht ausgeführt, und falsch (.F.) wird sofort zurückgegeben.

-1

Wenn <AusdrN> -1 ist, setzt FoxPro die Satz/Dateisperrversuche auf unbestimmte Zeit fort. Sie können die Sperrversuche nicht mit ESC abbrechen, und es wird keine ON ERROR-Routine ausgeführt. Eine Systemmeldung ("Warte auf Sperre...") wird nur angezeigt, wenn SET STATUS ON ist. Wurde der Satz/die Datei, die Sie sperren möchten, von einem anderen Benutzer gesperrt, müssen Sie solange warten, bis dieser Benutzer die Sperre aufhebt.

<AusdrN> > 0 [SECONDS]

Ist der SET REPROCESS-Wert <AusdrN> größer als 0, können Sie bestimmen, wie oft und wie lange FoxPro Sperrversuche bei einem Satz bzw. einer Datei ausführt. Um die Anzahl der Sperrversuche anzugeben, lassen Sie das Schlüsselwort SECONDS aus. Um die Dauer der Sperrversuche festzulegen, begeben Sie SECONDS an.

Wenn <AusdrN> 30 ist, versucht FoxPro einen Satz oder eine Datei bis zu 30 mal zu sperren. Wird auch das optionale Schlüsselwort SECONDS (SET REPROCESS TO 30 SECONDS) angegeben, wird der Satz- bzw. Dateisperrversuch bis zu 30 Sekunden lang ausgeführt.

Eine Systemmeldung ("Warte auf Sperre...") wird nur angezeigt, wenn SET STATUS ON ist.

Ist eine ON ERROR-Routine angegeben und sind die Sperrversuche erfolglos, wird die ON ERROR-Routine ausgeführt. Führt jedoch eine Funktion den Sperrversuch aus, wird eine ON ERROR-Routine nicht ausgeführt, und die Funktion gibt falsch (.F.) zurück.

Wenn keine ON ERROR-Routine aktiv ist und die Satz- bzw. Dateisperre erfolglos ist, wird eine entsprechender Warnmeldung angezeigt (z.B. "Satz wird bereits von anderem Benutzer benutzt"). Führt jedoch eine Funktion eine Sperre aus, wird die Warnmeldung nicht angezeigt, und die Funktion gibt falsch (.F.) zurück.

TO AUTOMATIC

Ist SET REPROCESS TO AUTOMATIC (oder SET REPROCESS TO -2), führt FoxPro die Satz- bzw. Dateisperre auf unbestimmte Zeit aus. Währenddessen wird die Systemmeldung "Versuche zu sperren... mit ESC abbrechen" angezeigt. Wird der Satz bzw. die Datei zur Sperrung freigegeben, während Sie warten, wird die Sperre ausgeführt und die Systemmeldung gelöscht. Erfolgt die Sperre mit einer Funktion, wird wahr (.T.) zurückgegeben.

Wenn keine ON ERROR-Routine angegeben ist und Sie bei Anzeige der Systemmeldung ESC drücken, erscheint eine entsprechende Warnmeldung (z.B. "Satz wird bereits von anderem Benutzer benutzt"). Erfolgt die Sperre jedoch durch eine Funktion, wird die Warnmeldung nicht gezeigt und falsch (.F.) zurückgegeben.

Wenn eine ON ERROR-Routine angegeben ist und ESC gedrückt wird, wird die Routine ausgeführt. Bei einem Sperrversuch mit einer Funktion wird die ON ERROR-Routine nicht ausgeführt und falsch (.F.) zurückgegeben.

SYS(3051, [nWaitMilliseconds])

Damit kann man den zeitlichen Abstand zwischen zwei Sperrversuchen feintunen. Neu ab VFP5. Der Default sind 333 mSec, d.h. der SET REPROCESS versucht 3x in der Sekunde ein Lock zu bekommen. Gültige Werte sind 100 bis 1000 (mSec).

Ohne zweiten Parameter wird der aktuelle Wert zurückgegeben.

SYS(3052, nFileType, [lHonorReprocess])

Mit dieser neuen VFP5 Funktion kann man Foxpro dazu zwingen, beim Sperren von Index- und Memo-Dateien ebenfalls die SET REPROCESS Einstellungen zu verwenden. Im Normalfall wartet FoxPro bei CDX und FPT Sperren unbegrenzt auf Freigabe, was bei parallel laufenden Transaktionen zu einem sog. Deadlock führen kann (d.h zwei Stationen blockieren sich gegenseitig).

Falls sie Transaktionen verwenden, sollten Sie folgende Einstellungen beim Programmstart absetzen:

=SYS(3052, 1, .T.)
=SYS(3052, 2, .T.)
SET LOCK [ON | OFF]

Mit SET LOCK können Sie die automatische Dateisperre aktivieren/deaktivieren. Bei der Ausführung von bestimmten Befehlen sperrt FoxPro normalerweise nicht die Dateien, wenn nur lesend auf eine Tabelle zugegriffen wird. Die folgende Liste enthält die Befehle, die einen Nur-Lese-Zugriff auf eine Tabelle durchführen:

AVERAGE LIST
CALCULATE LABEL
COPY TO REPORT
COPY TO ARRAY SORT
COUNT SUM
DISPLAY (mit einem Bereich) TOTAL
INDEX JOIN (xbase, beide Dateien)

Normalerweise kann die Tabelle während der Bearbeitung mit diesen Befehlen noch von anderen Anwendern im Netzwerk benutzt werden. Es ist daher möglich, daß die Tabelle während der Ausführung eines dieser Befehle geändert wird. Wenn Sie z.B. einen Bericht drucken und ein anderer Benutzer einen schon im Bericht enthaltenen Satz ändert, enthält Ihr Bericht dann nicht mehr die neuesten Daten und es können so Inkonsistenzen auftreten.

Der Standard ist SET LOCK OFF.

ON

Um sicherzustellen, daß die neuesten Daten verwendet werden, benutzen Sie SET LOCK ON. Dabei sperren dann die oben aufgeführten Befehle automatisch die Tabelle. Andere Benutzer im Netzwerk können die Tabelle dann nur noch lesen.

Dasselbe Ergebnis erhalten Sie durch Verwendung FLOCK( ) und anschließendem REPORT FORM und UNLOCK. In diesem Fall erfolgt die Dateisperre dann nicht automatisch.

OFF

Mit SET LOCK OFF kann der gemeinsame Zugriff auf Tabellen mit den oben aufgeführten Befehlen aktiviert werden. SET LOCK OFF können Sie benutzen, wenn Sie nicht unbedingt die aktuellsten Daten zur Verfügung haben müssen.

SET MULTILOCKS [ON | OFF]

Festlegen, ob mit LOCK() oder RLOCK() das gleichzeitige Sperren mehrerer Sätze möglich ist.

Ist eine Tabelle für gemeinsamen Zugriff im Netzwerk geöffnet, können Sie versuchen, mehr als einen Satz einer Tabelle zu sperren. Entsprechend der Einstellung von SET MULTILOCKS können Sie einen Sperrversuch an einem oder mehreren Sätzen vornehmen.

HINWEIS

Beim Umschalten von SET MULTILOCKS von ON auf OFF oder umgekehrt wird automatisch ein UNLOCK ALL ausgeführt , d.h. alle Satzsperren in allen Arbeitsbereichen werden aufgehoben.

Die Standardeinstellung ist SET MULTILOCKS OFF.

ON

Bei SET MULTILOCKS ON können Sie versuchen, mehrere Sätze zu sperren. Dies ist auch durch Angabe einer Reihe von Satznummern in LOCK() oder RLOCK() möglich.

OFF

Ist SET MULTILOCKS OFF gesetzt, wird beim aktuellen Satz ein einzelner Satzsperrversuch mit LOCK() oder RLOCK() vorgenommen.

FLOCK([<AusdrN> | <AusdrZ>] )

Mit FLOCK() wird versucht, eine Tabelle zu sperren. Bei erfolgreichem Sperrversuch einer Tabelle wird wahr (.T.) zurückgegeben. Wenn die Datei oder ein Satz der Tabelle schon von einem anderen Benutzer gesperrt wurde, wird falsch (.F.) zurückgegeben. Es wird jedoch kein Fehler generiert. Aus diesem Grunde können Sie mit FLOCK( ) keine ON ERROR-Routine starten.

Bei erfolgreicher Dateisperre kann die Datei von dem Benutzer zum Lesen und Schreiben benutzt werden, der die Datei gesperrt hat. Für die anderen Benutzer im Netzwerk ist die Tabelle schreibgeschützt.

Eine Tabelle bleibt so lange gesperrt, bis die Sperrung durch den Benutzer, der die Sperrung verursacht hat, wieder aufgehoben wird. Bei Ausgabe von UNLOCK, Schließen der Tabelle oder Verlassen von FoxPro kann die Tabellensperrung wieder aufgehoben werden.

Standardmäßig versucht FLOCK( ) einmal, eine Datei zu sperren. Wenn automatisch ein erneuter Dateisperrversuch ausgeführt werden soll, falls der erste Versuch fehlschlägt, ist SET REPROCESS zu benutzen. Wenn ein Sperrversuch das erste Mal nicht erfolgreich war, wird mit SET REPROCESS die Anzahl der Sperrversuche und die Zeitdauer eines Sperrversuchs bestimmt

<AusdrN> | <AusdrZ>

Wenn Sie keinen Arbeitsbereich oder Aliasnamen angeben, versucht FLOCK(), die Tabelle im aktuellen Arbeitsbereichs zu sperren. Geben Sie die Arbeitsbereichsnummer <AusdrN> oder den Aliasnamen <AusdrZ> an, wenn eine Tabelle in einem anderen Arbeitsbereich gesperrt werden soll.

RLOCK([nAlias | cAlias]) oder RLOCK(cRecNrList, [nAlias|cAlias])

Mit RLOCK() wird versucht, einen Datensatz oder eine Gruppe von Sätzen zu sperren. RLOCK() ist identisch mit LOCK().

Bei erfolgreicher Sperre wird wahr (.T.) zurückgegeben. Der Benutzer, der die Sperre verursacht hat, hat Lese- und Schreibzugriff auf die gesperrten Sätze. Alle anderen Benutzer im Netzwerk haben Nur-Lese-Zugriff auf die Sätze.

Die Ausführung von RLOCK() garantiert nicht, daß der/die Sperrversuch/e erfolgreich ist bzw. sind. Ein Satz, der schon von einem anderen Benutzer gesperrt wurde, kann nicht gesperrt werden. Ein Satz in einer von einem anderen Benutzer gesperrten Tabelle kann ebenfalls nicht gesperrt werden. Wenn die Sätze nicht gesperrt werden können, wird falsch (.F.) zurückgegeben.

Standardmäßig versucht RLOCK( ) einmal, einen Satz zu sperren. Benutzen Sie SET REPROCESS, um weitere Satzsperrversuche auszuführen, wenn der erste Versuch nicht erfolgreich war. SET REPROCESS bestimmt, wie oft oder wie lange nach einem ersten nicht erfolgreichen Versuch die Satzsperrversuche fortgesetzt werden.
Aufpassen: Die Parameter bei RLOCK erfordern einige Intelligenz! Wird nur ein Parameter angegeben, ist der ALIAS der erste Parameter, bei zwei Werten ist der ALIAS der zweite Parameter!

<nAlias | cAlias>

Wenn kein Arbeitsbereich oder Aliasname angegeben wird, versucht RLOCK() den aktuellen Satz der Tabelle im gewählten Arbeitsbereich zu sperren.

Beim Versuch, den aktuellen Satz in einer Tabelle zu sperren, die nicht im gewählten Arbeitsbereich geöffnet ist, ist die Arbeitsbereichnummer oder der Aliasname der Tabelle anzugeben.

<cRecList>

Um mehrere Sätze zu sperren, ist der anzugeben. SET MULTILOCKS muß ON sein und sie müssen den Arbeitsbereich oder den Aliasnamen der Tabelle angeben, in der mehrere Sätze gesperrt werden sollen. ist eine Liste einer oder mehrerer Satznummern, die jeweils durch Komma getrennt sind. RLOCK() versucht dann, alle angegebenen Sätze zu sperren. Wenn z.B. die ersten vier Sätze einer Tabelle gesperrt werden sollen, muß '1,2,3,4' enthalten.

Sie können mehrere Sätze auch sperren, indem Sie den Satzzeiger auf den zu sperrenden Satz bewegen, RLOCK() oder LOCK() ausführen und dann diesen Vorgang für jeden zu sperrenden Satz wiederholen.

Sind alle in angegebenen Sätze erfolgreich gesperrt, gibt RLOCK() wahr (.T.) zurück. Wenn einer oder mehrere in angegebenen Sätze nicht gesperrt werden können, wird falsch (.F.) zurückgegeben, und keiner der Sätze wird gesperrt. Alle schon bestehenden Satzsperren bleiben jedoch erhalten. Beim Sperren von mehreren Sätzen werden zusätzliche Satzsperren eingefügt, hierdurch werden jedoch keine anderen Satzsperren aufgehoben.

Hinsichtlich der Leistung ist das Sperren der gesamten Tabelle immer schneller als das Sperren selbst einer kleinen Anzahl von Sätzen.

Der Wert "0" hat eine besondere Bedeutung: Damit kann der Datei-Header gesperrt werden, d.h. Neuanlagen anderer werden gesperrt. Siehe Tips und Tricks!

LOCK( )

LOCK() ist identisch mit RLOCK().

SYS( 0 )

Mit SYS(0) werden Rechnername und Rechnernummer als Zeichenkette zurückgegeben, wenn Sie FoxPro im Netz installiert haben. Rechnername und -nummer müssen zunächst von der Netzwerksoftware zugewiesen werden.

Unter Novell wird dies im Login-Script mittels der MACHINE Zuweisung erledigt:

MACHINE = "%LOGIN_NAME # %P_STATION"
? SYS(0) => "WOODY # 4000E345 # 255"

Wenn Rechnername oder -nummer nicht zugewiesen oder die Netzwerkbenutzeroberfläche nicht geladen ist, gibt SYS(0) eine Zeichenkette zurück, die 15 Leerzeichen, ein Schraffurzeichen (#), gefolgt von einem weiteren Leerzeichen und 0 enthält.

=> " # 0"

Unter Windows erhalten Sie den Namen ihres Arbeitsplatzes, die Raute und den Namen des angemeldeten Bedieners:

=> "WOODYS RECHNER # woody"

SYS( 2011 )

Mit SYS(2011) wird der aktuelle Satz- oder Dateisperrstatus des aktuellen Arbeitsbereichs zurückgegeben. Im Gegensatz zu FLOCK(), LOCK() und RLOCK() führt SYS(2011) keinen Datei- oder Satzsperrversuch aus, sondern berichtet nur den momentanen Zustand. D.h. SYS(2011) gibt "Exklusiv" nur an dem Rechner zurück, von dem aus die Tabelle exklusiv geöffnet wurde, und "Satz gesperrt" auch nur an dem Rechner, von dem aus der Satz gesperrt wurde.

Die von SYS(2011) zurückgegebene Zeichenkette ist identisch mit der in der Statuszeile angezeigten Meldung (Exklusiv, Satz nicht gesperrt, Satz gesperrt ...). Aufpassen: Die zurückgegebenen Texte sind auch abhängig von der Sprachversion des verwendeten FoxPro Kerns. (Wichtig bei internationalen Programmen!)

Besser ist daher in VFP5 die Verwendung von:

ISEXCLUSIVE(<Alias>)

Seit VFP5 haben wir diese neue Funktion, die nur .T. bzw .F. zurückgibt, ansonsten gelten die bei SYS(2011) genannten Einschränkungen.

ISRLOCKED(nRecno, <Alias>)

Gibt den lokalen Lockstatus für einen Datensatz zurück. Es kann damit nicht der eventuell mögliche Lock-Erfolg im Netzwerk vorsorglich getestet werden! Neu seit VFP5.

ISFLOCKED(<Alias>)

Gibt den lokalen Lockstatus für eineDateisperre zurück. Es kann damit nicht der eventuell mögliche Lock-Erfolg im Netzwerk vorsorglich getestet werden! Neu seit VFP5.

UNLOCK [RECORD nRecNo] / [IN <AusdrN> | <AusdrZ> ] / ALL

UNLOCK hebt eine oder mehrere Satzsperre(n) oder eine Dateisperre der aktuellen Tabelle/.DBF, einer Tabelle in einem anderen Arbeitsbereich oder von Tabellen in allen Arbeitsbereichen auf. Satz- und Dateisperren bei Tabellen können nur von dem Benutzer wieder aufgehoben werden, der die Sperren gesetzt hat. Für alleinigen Zugriff geöffnete Tabellen können mit diesem Befehl nicht freigegeben werden.

Bei Verwendung von UNLOCK ohne zusätzliche Argumente wird eine Satzsperre (bzw. Sperren mehrerer Sätze) oder eine Dateisperre der Tabelle im aktuellen Arbeitsbereich aufgehoben.

RECORD

Hebt die Satzsperre dieses einen Datensatzes auf. Mit Angabe von Satznummer 0 können sie ein HeaderLock wieder aufheben.

IN <AusdrN> | <AusdrZ>

Wenn kein Arbeitsbereich oder Alias angegeben ist, wird mit UNLOCK eine Satzsperre (bzw. Sperren mehrerer Sätze) oder eine Dateisperre einer Tabelle des aktuellen Arbeitsbereiches aufgehoben. Zur Freigabe einer Satzsperre bzw. einer Dateisperre einer Tabelle in einem anderen Arbeitsbereich ist dessen Nummer <AusdrN> oder der Tabellenalias <AusdrZ> anzugeben.

ALL

ALL hebt alle Satz- oder Dateisperren in allen Arbeitsbereichen auf.

USE [EXCLUSIVE | SHARED | NOUPDATE]

EXCLUSIVE

EXCLUSIVE öffnet eine Tabellendatei für alleinigen Zugriff in einem Netzwerk, auch wenn SET EXCLUSIVE auf OFF steht.

SHARED

Geben Sie SHARE an, um eine Tabelle für den gemeinsamen Zugriff im Netzwerk zu öffnen. SHARE erlaubt Ihnen, eine Tabelle für den gemeinsamen Zugriff zu öffnen, auch wenn SET EXCLUSIVE auf ON ist.

NOUPDATE

Die Angabe von NOUPDATE verhindert Änderungen an einer Tabelle. Dieser Modus empfiehlt sich auch aus Performancegründen, z.B. bei allen nicht änderbaren Lookup-Tabellen.

NETWORK( )

Rückgabe von wahr (.T.), wenn Sie die gute, alte FoxPro/LAN 2.0, die Netzwerkversion von FoxPro/DOS, benutzen. Ansonsten nutzlos, da nur aus Kompatibilität vorhanden. Seit FoxPro 2.5 immer .T. (Unter FoxBase, FoxPro1 und FoxPro2 gab es gesonderte SingleUser und MultiUser Versionen)

Eine verbesserte NETWORK() Funktion könnten Sie so realisieren:

**********************************
FUNCTION IsNetwork
**********************************
LOCAL lcTempName, llTest*
lcTempName = SUBSTR(SYS(2015),3)+".DBF"
CREATE TABLE (lcTempName) (Schrott C(1))
USE (lcTempName) SHARED
llTest = "NICHT" $ UPPER(SYS(2011))
llTest = NOT ISEXCLUSIVE() && nur VFP5
USE
DELE FILE (lcTempName)
RETURN llTest

Diese Funktion macht sich zu nutze, daß eine Datei trotz Angabe von SHARED immer im Exklusiv Modus geöffnet wird, wenn keine Netzwerkunterstützung vorhanden ist (SHARE, NETX usw.)

Weitere Tips und Tricks

Teilupdates einer Tabelle verhindern

Bei aktiviertem TableBuffering werden mit TABLEUPDATE die Änderungen mehrerer Datensätze weggeschrieben. Wenn einer der Updates nicht greift (Meist ein Triggerfehler), wird der TABLEUPDATE sofort abgebrochen, und lässt sie mit teilweise geschriebenen und ungeschriebenen Updates zurück. Hier können Sie mit einer Transaktion das ganze 100% wasserfest machen:

BEGIN TRANSACTION
IF TABLEUPDATE(.T.,.T.)
END TRANSACTION
ELSE
ROLLBACK
ENDIF

Feststellen des Ändern-Modus

Wie schon weiter vorne festgestellt, ist GETFLDSTATE nur dann wirklich 100% sicher, wenn sie während der Eingabe sicherstellen können, daß die Form nicht verlassen werden kann via Toolbar, Menü , oder ON KEY LABEL. Da dies eher unwahrscheinlich ist, sollten Sie in ihrer Form entweder ein eigenes Property festlegen, das den momentanen Eingabezustand definiert, oder aber korrigierend auf VFP einwirken.

Das eigene Edit-Flag kann im InteractiveChange Event der Textbox gesetzt werden:

IF NOT THISFORM.EditMode
THISFORM.EditMode = .T.
ENDIF

Später beim Blättern oder Verlassen der Maske brauchen Sie nur dieses Flag abfragen. Dies ist außerdem sehr nützlich, wenn Sie diverse Toolbar Buttons, Menüoptionen usw enablen/disablen wollen.

Die andere Möglichkeit (VFP korrigieren), funktioniert ebenfalls über den InteractiveChange Event, indem wir das VFP interne Edit-Flag, das beim GETFldState angezeigt wird, mittels der Gegenfunktion SETFldState von uns aus schon beim ersten Tastendruck setzen:

=SETFLDSTATE(SUBS(this.controlsource, AT(".",this.controlsource)+1),;
2,;
LEFT(this.controlsource, AT(".",this.controlsource)-1))

Timeout von Forms

In der FPW Welt hatten wir das READ TIMEOUT, um schlafende Anwender aus der Datensatzdauersperre zu werfen. Unter VFP gibt es kein Property mit diesem Effekt. Dafür aber haben wir das Timerobjekt, das man ganz einfach mit auf die Form setzt.

Wichtig dabei: TimerEvents treten nicht auf, wenn das Menü aktiviert ist, eine MESSAGEBOX Funktion aufgerufen ist, oder wenn die Form im Verschiebemodus ist (Gedrückte Maustaste auf Titelleiste, bzw Systemmenü angewählt). Unter diesen Umständen werden alle zwischenzeitlichen TimerEvents gepuffert, sodaß sie nach Freigabe alle hintereinander loslegen.

Unbekannte DataSession feststellen

Wenn Sie eine Form mit aktivierter PrivateDatasession beenden, schließt VFP automatisch die im Dataenvironment angegebenen Dateien. (Wenn Sie das DE nicht verwenden, steht ihr dafür zuständiger Code im UNLOAD Event). Falls dabei aber ein Fehler auftritt(zB TriggerFehler), wird die Form zwar geschlossen, aber die DataSession mitsamt den offenen Dateien bleibt bestehen. Dasselbe werden Sie sicherlich schon beim Entwickeln erlebt haben: Ein CLOSE ALL CLEAR ALL bringt immer wieder ne Fehlermeldung, deren Ursache noch nicht weggeschriebene Änderungen sind. Sie können nicht mal VFP beenden, solange dieses Problem nicht behoben ist....Zwar ist dieses Phänomen nun mit der VFP5 behoben, in der VFP3 muß man aber noch weiterhin dafür Sorge tragen.

Um da rauszukommen, müssen sie diese DataSession aktivieren (SET DATA TO ..), und dann mit TABLEREVERT() aufräumen. Am einfachsten geht das über das SET (bzw Umgebungsfenster), wo sie die übriggebliebenen DataSessions raussuchen können. Komfortabler geht´s mit folgendem Code:

ON ERROR lnError = ERROR()
lnError = 0
FOR lnDataSessionID = 1 TO 1000
SET DATASESSION TO lnDataSessionID
IF lnError = 0
DO WHILE TXNLEVEL() > 0
ROLLBACK
ENDDO
IF lnError = 0 AND AUSED(laTables) > 0
FOR lnTables = 1 TO ALEN(laTables,1)
IF CURSORGETPROP("Buffering", laTables(lnTables,1)) > 1
SELECT (laTables(lnTables,1))
= TABLEREVERT()
USE
ENDIF
ENDFOR
ENDIF
RELEASE laTables
ENDIF
IF lnError > 0
SET DATASESSION TO 1
ON ERROR
EXIT
ENDIF
ENDFOR

Nächste Nummer generieren

Im normalen Programmiererleben gehört dieser Job zu den immer wiederkehrenden Aufgaben: Wie vergebe ich am saubersten die fortlaufenden Nummern bzw Primery Keys für Adressen, Aufträge usw. Dazu bieten sich mehrere Möglichkeiten an:

  1. Generieren einer eindeutigen Nummer mittels komplizierter Formeln
  2. Abspeichern der zuletzt verwendeten Nummer in einer Systemtabelle
  3. Hochzählen der höchsten Nummer

Version 1 hat den Nachteil, daß die generierten Ids in keinerlei Sequenz auftauchen, und daher für Menschen schwer zu verfolgen sind.

Version 2 ist die meist verwendete, hat aber das problem, daß die Systemtabelle und die Datendateien bei Backups immer synchron gehalten werden müssen. D.h. wenn das backup einer Datei eingespielt wird, muß der nächste Wert in der Systemtabelle mit angepasst werden! Ansonsten gibt’s Ärger mit doppelten Nummern....

Version 3 ist simpel, solang man nicht im Netzwerk arbeitet:

GO BOTTOM
INSERT INTO VALUES (<Schlüsselfeld> + 1)

Im Netzwerk gibt’s hier erhebliche Probleme, da natürlich 10 Leute gleichzeitig GO BOTT machen können...

In Visual FoxPro geht's nun aber sehr elegant:

SET ORDER TO DESCEND && Absteigend!
LOCATE && Rushmore optimiertes GO TOP!
DO WHILE NOT RLOCK("0",ALIAS()) && Header sperren
ENDDO
INSERT INTO (ALIAS()) VALUES ( + 1)
UNLOCK RECORD 0

Durch die Headersperre ist sichergestellt, daß immer nur ein Anwender einen neuen Datensatz anlegt. Der Trick mit dem LOCATE beruht darauf, daß bei einem event. gesetzten Filter ein GO TOP nicht optimierbar ist, wohingegen ein LOCATE optimiert auf den ersten gültigen Datensatz springt. Leider gibt es kein optimierbares Gegenstück für GO BOTTOM, sodaß wir daher einfach die Sortorder auf absteigend umschalten (SET ORDER ASCEND), und dann auf den Anfang springen. Somit sind wir auf der zuletzt verwendeten Nummer, addieren einen drauf und legen einen neuen Datensatz an.