Programmieren in VFP, Teil 1

Es war einmal eine kleine, feine Gemeinde von xBase-Programmierern, die sich in der zweiten Hälfte der 80er Jahre um den Fuchs scharte. Ihr bevorzugtes Entwicklungswerkzeug war FoxBase, ein xBase-Derivat, aber eben nicht ein beliebiges, sondern von Anfang an das schnellste. Schon die Folgeversion von FoxBase, FoxPro 1.0 aus dem Jahre 1989 kümmerte sich nicht mehr ausschließlich um xBase-Kompatibilität, sondern ging eigene Wege. FoxPro 2.5 und 2.6 für Windows, seit 1992 unter der Flagge von Microsoft entwickelt, setzte die Absetzbewegung fort. Spätestens ab Visual FoxPro 3.0, seit August ‘95 auf dem deutschen Markt erhältlich, ist xBase-Kompatibilität nur mehr Geschichte. Abwärtskompatibilität wird gewahrt, ansonsten stellt sich Visual FoxPro, jetzt in der Version 6.0 erhältlich, als ein Entwicklungswerkzeug dar, das eine hervorragende DatabaseEngine, eine objektorientierte Programmiersprache und herausragende Client/Server-Fähigkeiten miteinander verbindet. Die schnellste Windows-Datenbank ist FoxPro aber immer noch.

Visual FoxPro (VFP) bietet dem Entwickler viele neue Möglichkeiten und stellt gleichzeitig erhöhte Anforderungen. Zwischen der Vorversion FoxPro 2.6 und Visual FoxPro liegt ein Generationensprung, der unter anderem dafür verantwortlich ist, daß viele Entwickler noch immer sich davor scheuen, ihre Applikationen auf die Visual FoxPro Version umzuschreiben. Allerdings: Zu umfangreich sind die Änderungen gegenüber der Vorversion, als daß sich bestehende FoxPro 2.x Applikationen so einfach umstellen ließen.

Die Unterschiede zu FoxPro 2.x betreffen fast alle Bereiche der Entwicklung:

Die Einführung des DBC, der Datenbank-Hülle, die die Tabellen zusammenfasst, ermöglicht Gültigkeitsregeln auf Satz- und Feldebene, das Festlegen von Defaultwerten pro Feld und die Erstellung von Insert-, Delete- und Update-Triggern. Neu seit VFP 5.0 ist Field-To-Class-Mapping, mit dem Tabellenfelder an vordefinierte Klassen gebunden werden können, sowie das DataMapping, mit dem Datentypen des Servers in VFP-eigene Datentypen umgewandelt werden.

Neu ist die Möglichkeit, updatable Views zu verwenden, also aus beliebigen Feldern der Basistabellen  virtuelle Tabellen zusammenzustellen, die dem Anwender oder dem Anwendungsprogramm eine änderbare Teilansicht der Grunddaten zur Verfügung stellt. Die Grunddaten können dabei aus lokalen, das heißt FoxPro eigenen Tabellen, oder entfernten, über ODBC verknüpften Tabellen einer anderen Datenbank stammen.

Visual FoxPro unterstützt objektorientiertes Programmieren. Neue Sprachelemente ermöglichen die Definition von Klassen, Erzeugen, Verwalten und Freigeben von Objekten, das Anlegen von Klassenbibliotheken und vieles mehr.

Die Erstellung der Anwendungsoberfläche hat sich gegenüber FoxPro 2.x grundsätzlich gewandelt und ähnelt jetzt sehr der von Visual Basic bekannten Vorgehensweise. Die Masken zur Ein- und Ausgabe von Daten heißen jetzt Formulare. Alle Elemente in diesen Formularen liegen als Objekte vor, deren Eigenschaften eingestellt werden können, um das gewünschte Erscheinungsbild zu erhalten.

Neben den einstellbaren Eigenschaften ermöglichen die Objekte der Anwendungsoberfläche durch Angabe von Methoden eine anwendungsspezifische Reaktion auf eintretende Ereignisse. Dabei hat sich die Zahl der Ereignisse, auf die reagiert werden kann, gegenüber FoxPro 2.x vervielfacht.

Ein völlig neuer, an Visual C++ angelehnter Debugger erleichtert die Fehlersuche in VFP.

Seit Version 5.0 kann VFP nun auch als OLE-Server arbeiten. Darüber hinaus ist es möglich, VFP-Klassen als OLE Automation Server DLL zu generieren. Die aus diesen Klassen erzeugten Objekte lassen sich zum Beispiel von Excel nutzen, ohne daß die komplette VFP-Umgebung auf dem Zielrechner installiert sein müßte.

Mit VFP erstellte Programme sind 32-Bit Programme, VFP 5 und 6 unterstützen aus Performancegründen auch Win32s nicht mehr. Der Schnitt bewirkt, daß bei deutlich geringerer Hauptspeicheranforderung die Performance vor allem der Bildschirmausgaben teilweise um den Faktor 10 erhöht werden konnte.

Natürlich kann man mit Visual FoxPro ähnlich prozedural entwickeln, wie früher mit FoxPro 2.x. Selbst die @SAY ... und @GET ... Befehle mitsamt dem dazu gehörenden READ haben aus Gründen der Abwärtskompatibilität überlebt und ermöglichen dem verzweifelten xBase-Programmierer Momente des glücklichen Wiedererkennens. Wer die Vorteile, die Visual FoxPro bietet, aber konsequent nutzen will und obendrein überzeugt vom Sinn eines Wechsels zum objektorientierten Programmieren ist, muß sich den gestiegenen Anforderungen stellen und die nicht gerade kurze Lernkurve bezwingen. Dabei will dieser Artikel Hilfestellung leisten.

Der erste Teil wird sich mit Aspekten der VFP Datenbank befassen. Dabei werde ich auf die verschiedenen Möglichkeiten der VFP Datenbankengine eingehen, also auf die Feldtypen von Visual FoxPro, auf Gültigkeitsregeln und Trigger, freie und gebundene Tabellen sowie Local- und Remote-Views.

Der zweite Teil wird der objektorientierten Programmierung mit Visual FoxPro gewidmet sein. Die Basisklassen von VFP, visuelle und nichtvisuelle Klassen, Containerklassen mit ihren Eigenschaften und Methoden werden vorgestellt. Die Umsetzung von OO-Konzepten wie Aggregation, Assoziation, Vererbung und Polymorphie sowie der Aufbau eigener Klassenbibliotheken wird besprochen.

Der dritte Teil wird die Erstellung der Anwendungsoberfläche zum Thema haben. Dabei stelle ich die verfügbaren Oberflächenelemente vor, gebe einige Hinweise zum Eventhandling mit Visual FoxPro und beschreibe die Möglichkeiten der Fehlerbehandlung.

Die Datenbank

Eine xBase Datenbank, wie die von FoxPro 2.x, bestand aus Tabellen und den dazugehörenden Indexdateien. Einen Überbau, der die zusammen gehörenden Tabellen zentral verwaltete, gab es nicht. Der Entwickler mußte in seinem Programm selbst dafür sorgen, daß Beziehungen geknüpft werden, neue Tabellensätze mit Standardwerten versorgt werden und daß zum Beispiel keine Lagerartikel gelöscht werden können, für die noch Bestellungen vorliegen. Eine solche zentrale Verwaltungsinstanz für die Tabellen einer Datenbank stellt Visual FoxPro mit dem DBC zur Verfügung. Die Abkürzung DBC steht für DataBase Container. Der DBC ist selbst eine Tabelle, in der alle die Angaben gespeichert werden, die im Tabellen- oder Index-Header nicht untergebracht werden können. Die einfachste Art, einen leeren DBC zu erzeugen, ist, im Projektmanager von Visual FoxPro auf der Seite „Daten“ die Zeile „Datenbanken“ zu aktivieren und dann den „Neu“ Knopf anzuklicken. Den daraufhin erscheinenden Datenbankdesigner erreichen Sie auch, indem Sie mit CREATE DATABASE einen leeren DBC erzeugen und diesen anschließend mit MODIFY DATABASE zur Bearbeitung aufrufen. Ein Klick mit der rechten Maustaste innerhalb des noch leeren Datenbankdesigners bringt ein Kontextmenü auf den Bildschirm. Der Menüpunkt „Neue Tabelle ...“ bringt uns dann in den Tabellendesigner.

Abbildung 1: der Tabellendesigner von Visual FoxPro

Ein Blick auf den Tabellendesigner zeigt alles, was Visual FoxPro beim Erstellen von Datenbanktabellen zu bieten hat, konzentriert auf. Bei den Tabelleneigenschaften fällt auf, daß ein Tabellenname vergeben werden kann, der unabhängig vom Dateinamen dieser Tabelle ist. Wenn die Tabelle geöffnet wird, so wird der Tabellenname als Aliasname benutzt. Ein Tabellenname kann maximal 128 Zeichen lang sein, was auch der fleißigste Tipper selten ausschöpfen wird. Diverse Eigenschaften der Tabelle wie auch einzelner Felder können vorgegeben werden und werden im DBC festgehalten.

Bevor wir jedoch zu diesen Eigenschaften kommen, lassen Sie uns einen Blick auf die Definition der Felder werfen. Feldnamen können jetzt ebenfalls 128 Zeichen lang sein. Visual FoxPro kennt gegenüber FoxPro 2.x einige neue Feldtypen, zum Beispiel binäre Zeichen- und Memofelder, die von der automatischen Codepage-Umsetzung ausgenommen sind, einen Feldtyp Datum/Zeit, der für Felder gewählt werden kann, die als Zeitstempel dienen, die numerischen Feldtypen Double und Integer sowie den Feldtyp Währung.

Die binären Zeichen- und Memofelder können zum Speichern von Strings genutzt werden, die keine Textstrings sind, also beim Umsetzen der Tabelle auf eine andere Codepage unverändert bleiben sollen. Sie bieten sich zum Speichern von verschlüsselten Passwörtern, Hexadezimalwerten oder Codeteilen bzw. Ausdrücken an, die mit EVALUATE() ausgewertet werden sollen.

Der Feldtyp Datum/Zeit belegt genau wie ein Datum 8 Bytes. Sie können mit Feldern dieses Typs auch die gleichen Rechenoperationen durchführen, wie mit Feldern vom Typ Datum. Zu beachten ist lediglich, daß Datum/Zeit-Felder mit Sekunden rechnen, während die kleinste Einheit bei reinen Datumsfelder nach wie vor der Tag ist. So ergibt zum Beispiel {14.04.96 22:10:13} - 1 den Datum/Zeit-Wert {14.04.96 22:10:12} während {14.04.96} - 1 den Datumswert {13.04.96} ergibt.

Für Tabellenfelder, die zu Rechenoperationen benötigt werden, sind die Feldtypen Double und Integer dem schon von FoxPro 2.x bekannten Feldtyp numerisch vorzuziehen. Während Felder vom Typ numerisch vor der Rechenoperation intern umgewandelt werden, ist dies bei Feldern vom Typ Double oder Integer nicht mehr nötig.

Der Feldtyp Währung belegt wie ein Double-Feld 8 Bytes und hat ein festes Format mit 15 Vor- und 4 Nachkommastellen.

.NULL.

Visual FoxPro stellt für unbestimmte Feldinhalte eine Konstante zur Verfügung, die .NULL. Dies hat sich auch in der Felddefinition niedergeschlagen. Hier können Sie nämlich angeben, ob ein Feld einen .NULL.-Wert annehmen kann, oder nicht. .NULL. bedeutet - es ist kein Wert vorhanden, unterscheidet sich also von einer 0 in einem numerischen Feld oder von einem Leerzeichen in einem Textfeld. Selbst ein Leerstring ist nicht gleich .NULL. sondern immer noch ein String. Durch die Verwendung von .NULL. können Sie abprüfen, ob zum Beispiel eine Preisangabe explizit auf 0 gesetzt wurde oder ob der Anwender einfach vergessen hat, den Preis einzugeben - also kein Wert vorhanden ist. .NULL. besitzt keinen Typ. Wenn Sie nach einer Zuweisung wie  LEER = {}  abfragen:   ? type(‘LEER’)  so erhalten Sie die Ausgabe  „D“  wie Datum, nach der Zuweisung  LEER = .NULL. ergibt die Abfrage:  ? type (‘LEER’)  immer noch „D“ - der einmal eingestellte Feldtyp bleibt also erhalten.

Ein Vergleich zweier Werte, von denen der eine .NULL. ist, liefert immer .NULL. als Ergebnis. Da einer der Vergleichswerte einfach nicht vorhanden ist, kann auch kein Ergebnis des Vergleichs vorhanden sein - eigentlich logisch. Leider wird diese Logik nicht ganz konsequent beibehalten. So liefern EMPTY(LEER) und ISBLANK(LEER) den Wert .F. wenn LEER gleich .NULL. ist. Zum Test auf einen .NULL.-Wert sollten Sie die Funktion ISNULL() benutzen. Diese Funktion ist dann besonders wichtig, wenn Sie in Ihrer Tabelle Felder mitführen, die .NULL. sein können. Sätze mit .NULL.-Werten kann man nämlich nicht über LOCATE FOR <Feldname> = .NULL. finden. Auch der Vergleich, den der LOCATE-Befehl mit den Feldern der Tabelle durchführt, liefert .NULL. zurück, wenn einer der Vergleichswerte - in unserem Fall die Vergleichskonstante - .NULL. ist. Die richtige Konstruktion muß also lauten LOCATE FOR ISNULL(<Feldname>).

Feldeigenschaften

Jedem Feld einer DBC-gebundenen Tabelle in Visual FoxPro können Sie acht Eigenschaften und einen Kommentar mitgeben. Im einzelnen sind dies:

Format und Input-Mask:

Die hier hinterlegten Vorgaben für die Ausgabe und Eingabe von Daten in das Tabellenfeld werden in Ihr Eingabeformular übernommen, wenn Sie das Tabellenfeld per Drag&Drop aus der Datenumgebung in Ihr Formular eintragen. Auch der BROWSE-Befehl benutzt die hier hinterlegten Einstellungen.

Überschrift (Caption):

Die hier eingetragene Bezeichnung des Feldes wird unter anderem vom BROWSE-Befehl als Spaltenüberschrift benutzt. Beim Drag&Drop des Tabellenfeldes aus der Datenumgebung in das Formular kann automatisch ein Label erzeugt werden, daß die Caption des Feldes beinhaltet. Die Formularassistenten von Visual FoxPro tragen diese Bezeichnung ebenfalls als Feldlabel in das Ein-/Ausgabeformular ein. Allerdings werden diese Label als Text, das heißt, fest verdrahtet im Formular eingetragen. Die Änderung einer Feldbezeichnung im Datenbankdesigner wirkt sich also nicht auf einmal erstellte Formulare aus. Wenn Sie das besser machen wollen, als die Formularassistenten, so können Sie während des Ladevorgangs eines Formulars die aktuellen Feldbezeichnungen mit Hilfe des DBGETPROP-Befehls aus dem DBC auslesen und somit die Label Ihres Formulars aktualisieren. Aber hier greife ich vor - zurück zu den Feldeigenschaften.

Display Library und Display Class:

Diese Einträge bewirken das oben bereits erwähnte Field-To-Class-Mapping. Zunächst einmal besteht die Möglichkeit, in den Optionen generelle Mappings vorzugeben. Damit legen Sie zum Beispiel fest, daß ein Feld vom Typ Character mit der Basisklasse Textbox oder Ihrer eigenen Textbox-Klasse dargestellt werden soll. Bei Character-Feldern, deren Mapping vom Standard-Mapping abweicht, können Sie die spezifische Klasse hier im Tabellendesigner angeben. Angebbar sind dabei die Klassenlibrary (VCX) sowie die spezielle Klasse aus dieser Library.
Das Mapping wird immer dann berücksichtigt, wenn Sie das Feld in ein VFP-Formular ziehen, gleichgültig ob aus der Datenumgebung oder aus dem Projekt.

Gültigkeitsregel (Rule):

Hier können Sie eine Regel in Form eines Ausdrucks hinterlegen, der angibt, wie der im Tabellenfeld eingetragene Wert auf Gültigkeit überprüft werden soll. Der Ausdruck muß einen Wert .T. oder .F. zurückliefern. Wenn ein Tabellenfeld zum Beispiel nur Datumswerte enthalten soll, die größer oder gleich dem aktuellen Systemdatum sind, könnten Sie dies über den Ausdruck <Feldname> >= date() abprüfen. Statt <Feldname> muß dabei natürlich der aktuelle Feldname angegeben werden. Komplexere Abprüfungen können auch in eigenen Funktionen durchgeführt werden, die bei Gültigkeit des abzuprüfenden Wertes ein .T. zurückliefern und ein .F., wenn der Wert im Tabellenfeld nicht gültig ist. Solche Funktionen werden am besten direkt in der Datenbank gespeichert (siehe „gespeicherte Prozeduren“ weiter unten).
Die Gültigkeitsregel wird sofort überprüft, sobald der Anwender das mit dem Tabellenfeld verbundene Eingabefeld verlassen will. Dies ist unabhängig von der Art der Pufferung (die verschiedenen Möglichkeiten der Pufferung beschreibe ich ebenfalls weiter unten) und gilt sowohl für Eingabemasken als auch für den BROWSE Befehl. Durch das Festlegen von Gültigkeitsregeln auf Feld oder Satzebene kann damit auch verhindert werden, daß über BROWSE oder durch andere Programme, die über ODBC auf die Tabelle zugreifen, nicht gültige Werte in ein Tabellenfeld geschrieben werden. Wenn Sie eine Gültigkeitsregel für ein Feld später nachtragen, so müssen alle bereits in der Tabelle gespeicherten Sätze dieser Regel entsprechen. Visual FoxPro prüft dies vor dem Speichern Ihrer neuen Regel ab und weigert sich, diese Regel zu speichern, wenn die Tabelle bereits Sätze enthält, die die Regel verletzen.

Gültigkeitstext (Message):

Hier kann ein Text hinterlegt werden, der ausgegeben wird, wenn der Eintrag in einem Tabellenfeld die zugehörige Gültigkeitsregel verletzt.

Standardwert (Default Value):

Diese Eigenschaft gibt an, welcher Wert bei einem APPEND BLANK in das Tabellenfeld eingetragen werden soll. Auch der INSERT-SQL Befehl trägt die Standardwerte in alle Tabellenfelder ein, die im Befehl nicht aufgeführt sind. Die Angabe eines Standardwertes ist immer dann wichtig, wenn Sie für das Feld eine Gültigkeitsregel angegeben haben, die leere Feldinhalte ausschließt. Ohne einen Standardwert, der der Gültigkeitsregel entspricht, könnten Sie sonst den Befehl APPEND BLANK bei ungepufferten Tabellen nicht mehr nutzen, da dieser sich weigert, einen Satz an die Tabelle anzuhängen, wenn ein Feld des Satzes seine Gültigkeitsregel verletzt.

Kommentar (Field Comment):

Eigentlich keine Feldeigenschaft, aber immer sehr nützlich. Da Sie den Inhalt des Feldkommentars mit dem DBGETPROP-Befehl zur Laufzeit sowohl lesen als auch mit DBSETPROP verändern können, läßt sich der Feldkommentar auch für beliebige andere Zwecke verwenden.

Tabelleneigenschaften

Die im vorigen Abschnitt besprochenen Eigenschaften können für das einzelne Tabellenfeld im DBC hinterlegt werden. Ebenso können aber auch Eigenschaften angegeben werden, die für die ganze Tabelle gelten. Diese sind:

Gültigkeitsregel auf Satzebene:

Ähnlich wie die Gültigkeit eines Feldwertes kann auch die Gültigkeit des ganzen Satzes überprüft werden, bevor dieser in die Datenbank geschrieben wird. Das ermöglicht die Überprüfung von Feldern, die voneinander abhängig sind. Eine solche Überprüfung von abhängigen Feldern ist auf Feldebene schwer zu machen, da die Prüfung erst sinnvoll durchgeführt werden kann, wenn alle Werte angegeben sind. Dank GUI und Mausbedienung wissen Sie als Entwickler aber nie, wann das der Fall ist - es sei denn, der/die Anwender/in will den gerade bearbeiteten Satz speichern. Deshalb führt Visual FoxPro die Prüfung der Gültigkeitsregel auf Satzebene direkt vor dem Speichern aus.
Hierbei ist jetzt aber die Frage der Pufferung wichtig. Wenn Sie, wie früher unter FoxPro 2.x, mit einer ungepufferten Tabelle arbeiten, wird die Gültigkeitsregel auf Satzebene sofort nach einer Änderung an einem Tabellenfeld überprüft. Dies führt unter Umständen zu einem Konflikt, da eine solche Regel ja voneinander abhängige Felder überprüft, die alle erst mit den richtigen Werten versorgt werden müßen. Ein satzweises Puffern ist also in den meisten Fällen erforderlich, wenn Sie eine Gültigkeitsregel auf Satzebene verwenden wollen.

Gültigkeitstext:

Auch für die Gültigkeitsregel auf Satzebene ist ein Text hinterlegbar, der ausgegeben wird, wenn die Regel verletzt wird. Dieser Text wird allerdings nur vom BROWSE-Befehl ausgegeben. Wenn Sie den Text auch aus Ihren Ein-/Ausgabeformularen heraus ausgeben wollen, müssen Sie ihn zunächst mit DBGETPROP() aus dem DBC in eine Variable auslesen.

Trigger:

Zu den Tabelleneigenschaften gehören auch die INSERT-, UPDATE- und DELETE-Trigger. Dies sind Ausdrücke, die jedesmal ausgewertet werden, wenn ein Datensatz angehängt, verändert oder gelöscht wird. Die Trigger-Ausdrücke müssen .T. oder .F. zurückgeben. Abhängig vom Rückgabewert wird der Befehl, der den Trigger ausgelöst hat, durchgeführt (bei Rückgabe .T.) oder nicht (Rückgabe .F.).
Triggerausdrücke rufen im allgemeinen in der Datenbank gespeicherte Prozeduren auf, die zum einen prüfen, ob die auslösende Aktion ausgeführt werden darf und zum anderen notwendige Folgeaktionen durchführen.
Trigger werden häufig dazu benutzt, die referentielle Integrität (RI) sicherzustellen. Ein solcher Trigger verhindert zum Beispiel, daß ein Lagerteil gelöscht wird, für das noch Bestellungen gespeichert sind. Der selbe Trigger sorgt auch dafür, daß beim Löschen eines Lagerteils alle Bewegungsdaten ebenfalls gelöscht werden und so weiter. RI-Trigger aufzubauen, ist nicht trivial. Visual FoxPro bietet einen Assistenten für referentielle Integrität an, der den erzeugten Code in Form von gespeicherten Prozeduren in der Datenbank ablegt und im Trigger-Ausdruck der jeweiligen Tabelle diesen Code aufruft. Der von diesem Assistenten generierte Code ist ausreichend, wenn a) keine zusammengesetzten Schlüssel verwendet werden und b) die Triggeraktionen auf Kaskadierung (weitergeben einer Lösch- oder Änderungsaktion an die untergeordnete Tabelle), Restriktion (Verhindern der Aktion) und Ignorieren beschränkt werden können. Zusatztools zu Visual FoxPro, wie das E-R Modelling Tool xCase bieten weitergehende Möglichkeiten an. So können von xCase erzeugte Trigger mit zusammengesetzten Schlüsseln umgehen. xCase bietet beim Löschen eines Satzes der übergeordneten Tabelle auch die Triggeraktion Nullify an, also das automatische Leeren des Fremdschlüssels in der untergeordneten Tabelle.

Indexarten

Visual FoxPro kennt vier Arten von Indizes: Primärindex, potentielle Primärindizes, eindeutige und einfache Indizes. Ein einfacher Index ist ein Index, wie er aus FoxPro 2.x bekannt war. Für einen einfachen Index wird keine Prüfung auf Eindeutigkeit vorgenommen. Einfache Indizes dienen hauptsächlich zur Unterstützung der Zugriffsoptimierung durch das Rushmore-Verfahren und zur Anzeige der Tabelleninhalten in bestimmten Sortierungen. Auch der „eindeutige“ Index, bisher als „unique“ bekannt und zur Sicherstellung der Abwärtskompatibilität unterstützt, wird nicht eigentlich auf Eindeutigkeit überprüft. Es wird lediglich bei mehrfachem Vorkommen eines Indexwertes nur das erste Vorkommen gespeichert und die weiteren ignoriert - ein Verfahren, dessen tieferer Sinn mir bisher verborgen geblieben ist.

Neu und wichtig sind die potentiellen Primärindizes. Diese werden jetzt nämlich tatsächlich auf Eindeutigkeit geprüft und Visual FoxPro weigert sich, einen Datensatz in einer Tabelle abzulegen, der die Eindeutigkeit eines potentiellen Primärindex verletzen würde. Der als tatsächlicher Primärindex ausgewiesene Index ist also nur Primus inter Pares, jeder potentielle Primärindex kann zum tatsächlichen Primärindex gemacht werden.

Persistente Beziehungen

Beziehungen zwischen DBC-gebundenen Tabellen können im Datenbankdesigner bereits festgelegt und im DBC gespeichert werden. Beim Öffnen einer Datenbank werden diese Beziehungen aber nicht automatisch aufgebaut. Dies ist nämlich die Aufgabe eines Datenumgebungs-Objektes, das jeder Ein-/Ausgabemaske und jedem Report zugeordnet werden kann. Beim Eintrag von Tabellen in einem Datenumgebungs-Objekt werden die zwischen den Tabellen bestehenden und im DBC gespeicherten Beziehungen übernommen. Auch der unten beschriebene Ansichtsdesigner nutzt die im DBC gespeicherten Informationen und baut die Beziehungen zwischen den gewählten Tabellen automatisch auf. Neben den persistenten, also dauerhaften Beziehungen besteht nach wie vor die Möglichkeit, temporäre Beziehungen mit SET RELATION TO ... INTO ... aufzubauen.

DBC-gebundene und freie Tabellen

Die oben beschriebenen Feld- und Tabelleneigenschaften gelten komplett nur für DBC-gebundene Tabellen. Tabellen, die nicht an einen DBC gebunden sind, werden freie Tabellen genannt. Visual FoxPro kann solche freien Tabellen sowohl im alten, d.h. FoxPro 2.x kompatiblen Format, als auch im neuen Format verarbeiten. Für freie Tabellen im alten Format gelten natürlich die neuen Feldformate, die längeren Feldnamen und anderen Eigenschaften nicht. Freie Tabellen im neuen Format können jedoch die neuen Feldtypen beinhalten. Lange Feldnamen, Gültigkeitsregeln, Feldbezeichnungen, Primärkeys und Trigger werden aber im DBC abgespeichert und stehen von daher für freie Tabellen nicht zur Verfügung. Sollte dies einmal erforderlich werden, helfen auch hier Zusatztools von Drittanbietern weiter. In dem oben bereits erwähnten E-R Modelling Tool xCase können freie Tabellen und DBC-gebundene Tabellen in einem Modell zusammengeführt werden, wobei RI-Code momentan ebenfalls nur für die DBC-gebundenen Tabellen generiert wird. In der angekündigten Professional Version soll RI-Code auch für die freien Tabellen generiert werden. Bei den für FoxPro verfügbaren Applikations-Generatoren, die ein eigenes Data-Dictionary mitführen, ist das seit jeher so und gilt auch für die Visual FoxPro Versionen. In Visual ProMatrix zum Beispiel, einem Applikations-Generator für Visual FoxPro, werden freie und DBC-gebundene Tabellen gleichberechtigt mit allen Eigenschaften und eigenem Integritätscode im eigenen DD geführt. Dafür hapert es hier mit der Integration zwischen DBC und Data-Dictionary, da für die Arbeit mit Visual ProMatrix ein DBC eigentlich gar nicht gebraucht wird.

Ansichten (Views)

Eine Ansicht, oder View, ist eine virtuelle Tabelle, in der Daten aus einer oder mehreren anderen Tabellen zusammengefasst sind. Eine Ansicht enthält nur die Felder der Basistabellen, die in der Definition der Ansicht vorgesehen sind. Datensätze der Basistabellen können zu einem Datensatz in der Ansicht gruppiert sein, ebenso ist es möglich, daß die Ansicht die Daten der Basistabellen in einer anderen Sortierung ausgibt.

Die Basistabellen einer Ansicht können lokale, das heißt FoxPro eigene Tabellen, oder entfernte, über eine ODBC-Verbindung erreichte Tabellen eines Datenbankservers sein. Eine vorher erstellte Ansicht kann selbst wieder Basistabelle einer weiteren Ansicht sein.

Bild 2: Der Ansichtsdesigner von Visual FoxPro

Zum Erstellen von Ansichten bietet Visual FoxPro einen Ansichtsdesigner an. Dort werden zunächst die Tabellen angegeben, die in die Ansicht einbezogen werden sollen. Aus diesen Tabellen können dann die benötigten Felder ausgewählt werden. Auf weiteren Seiten des Ansichtsdesigners können Sortierung und Gruppierung der abzurufenden Daten festgelegt werden. Auf der Seite UPDATE wird bestimmt, wie die Aktualisierung der Basisdaten durchgeführt werden soll. Aktualisierungen werden grundsätzlich nur gesendet, wenn das dafür vorgesehene Kontrollkästchen angekreuzt ist. Um Aktualisierungen senden zu können, muß für jede zu aktualisierende Tabelle ein Schlüsselfeld angegeben werden. Dieses Schlüsselfeld muß nicht unbedingt mit einem Index belegt sein (was sich aus Gründen der Zugriffsoptimierung aber anbietet) und muß nicht eindeutig sein. Die zu aktualisierenden Felder können ebenfalls einzeln angegeben werden.

Aktualisierungen werden wahlweise über einen SQL-UPDATE Befehl oder über einen SQL-DELETE mit anschließendem SQL-INSERT durchgeführt. Der Aktualisierungsbefehl kann vor dem Aktualisieren abprüfen, ob die Basisdaten zwischenzeitlich von anderen Anwendern verändert wurden. Diese Abprüfung erstreckt sich wahlweise a) nur auf die als Schlüsselfelder markierten Felder, b) auf die Schlüsselfelder und alle aktualisierbaren Felder oder c) auf die Schlüsselfelder und die tatsächlich geänderten Felder. Die Option, daß die Schlüsselfelder und ein Zeitstempel des jeweiligen Basisdatensatzes überprüft werden, ist nur bei Remote Views, also bei Ansichten von Daten eines über OBDC verbundenen Datenbankservers verfügbar.

Aus den Angaben im Ansichtsdesigner generiert Visual FoxPro das benötigte SQL-Statement, das zur Kontrolle auch angezeigt werden kann. Programmgesteuert kann eine Ansicht mit dem Befehl CREATE SQL VIEW <Name der Ansicht> AS SELECT ... FROM ... aufgebaut werden.

Client/Server Programmierung

Ansichten werden vom Programm wie FoxPro eigene Tabellen behandelt. Alle xBase-Befehle zum Navigieren innerhalb einer Tabelle, Ändern von Daten in einer Tabelle und Anhängen neuer Datensätze an eine Tabelle funktionieren auch für Ansichten. Es ist also ohne weiteres möglich, über eine Ansicht die Daten eines Datenbankservers mit FoxPro Befehlen zu manipulieren. Allerdings sollte man dabei immer im Auge behalten, daß diese Daten auf anderen Wegen zur Verfügung gestellt werden, als Daten aus lokalen Tabellen. Die Erstellung einer Client/Server-Anwendung mit Hilfe von Ansichten benötigt daher noch ein gehöriges Maß an Feineinstellungen, um zum Beispiel zu verhindern, daß mehrere Millionen Datensätze über das Netz geschickt werden, obwohl nur einige wenige gebraucht werden. Visual FoxPro bietet dazu unterschiedliche Hilfsmittel an. Das erste und wichtigste ist, daß Ansichten parametrisiert werden können. Mit vorangestelltem Fragezeichen können Speichervariablen oder Objekteigenschaften in die Auswahlkriterien aufgenommen werden. „?KundenNr“ veranlasst Visual FoxPro vor dem Senden des Abruf-Statements an den Datenbankserver, die Variable „KundenNr“ auszuwerten und deren aktuellen Inhalt in das zu sendene Statement einzufügen. Wenn die Variable „KundenNr“ nicht definiert ist, fordert Visual FoxPro den Anwender vor dem Senden des SQL-SELECTs auf, einen Wert für „KundenNr“ einzugeben. Normalerweise bestückt jedoch das Anwendungsprogramm die als Ansichtsparameter verwendeten Variablen, um nur eine begrenzte Auswahl an Daten abzurufen. Wenn sich der Wert für den Ansichtsparameter ändert, weil der Anwender nun zum Beispiel die Daten eines anderen Kunden sehen will, reicht ein Aufruf der REQUERY()-Funktion, um die Ansicht mit dem aktuellen Parameterwert neu erstellen zu lassen.

Bild 3: Der Verbindungsdesigner von Visual FoxPro

Entfernte Ansichten (Remote-Views) greifen über eine ODBC-Verbindungen auf eine andere Datenquelle zu. Zur Definition dieser Verbindungen bietet Visual FoxPro einen Verbindungsdesigner an. Dort werden Datenquelle, Benutzerkennung und Passwort oder alternativ ein Connection String hinterlegt. Der Verbindungsdesigner ermöglicht darüber hinaus die Festlegung von Parametern, die die Verbindung steuern oder optimieren. So kann zwischen synchroner und asynchroner Verarbeitung gewählt werden, Warnmeldungen des Servers können ausgeblendet werden, der Batch-Modus entscheidet darüber, ob alle Ergebnisse eines gesendeten Statements, das mehrere SELECTs enthält, gemeinsam zurückgeliefert werden oder einzeln, sobald der erste Ergebniscursor verfügbar ist. Die Wahl „automatische Transaktionen“ bewirkt, daß jedes gesendete SQL-Statement als eigene Transaktion aufgefasst wird. Ohne automatische Transaktionen muss die auf dem Server mit dem ersten Befehl angestoßene Transaktion vom Programm selbst mit einem COMMIT oder ROLLBACK beendet werden.

Bei der Erstellung eines Remote-Views können weitere Parameter angegeben werden, die die Performance beeinflussen. Dies ist zunächst die Anzahl der in einem Block abzurufenden Sätze (Fetch Size), die standardmäßig auf 100 eingestellt ist. Wenn 100 Sätze des Ergebnis gefunden wurden, wird die Kontrolle an das Programm zurückgegeben, das diese ersten Sätze bereits anzeigen oder verarbeiten kann. Im Hintergrund wird der Ergebniscursor dabei mit weiteren Sätzen gefüllt. Die maximale Anzahl der zu übertragenden Sätze ist eine Sicherung, die eingesetzt werden sollte, solange Sie nicht ganz sicher sind, daß Ihr Programm an keiner Stelle ungewollt alle Sätze einer Tabelle aus der Serverdatenbank abruft. Da Serverdatenbanken im allgemeinen keine Memofelder kennen, können Sie angeben, ab welcher Größe ein Feld als Memofeld betrachtet werden soll und ob Sie Memofelder eventuell getrennt abrufen wollen. SQL-Statements können der Serverdatenbank zur Prekompilierung übergeben und danach mehrfach abgerufen werden.

Die Definition der Verbindungen und  Ansichten  werden im DBC gespeichert. Zur Laufzeit können Sie die eingestellten Werte mit DBGETPROP() auslesen und größtenteils auch mit DBSETPROP() neu setzen.

SQL Pass-Through

Neben dem View-Support ermöglicht Visual FoxPro den Zugriff auf Serverdatenbanken über die SQL Pass-Through Technik. Mit SQLEXEC() kann jede gewünschte SQL-Anweisung an den Server abgeschickt werden. Das erlaubt einen direkten Zugriff auf einstellbare Servereigenschaften, Definition und Änderung von Tabellen auf dem Server und Zugriff auf gespeicherte Prozeduren des Servers. Ansichten und SQL Pass-Through können kombiniert werden. Zusammen mit der sehr schnellen, robusten Datenbankengine von Visual FoxPro machen sie Visual FoxPro zu einer äußerst leistungsfähigen Client/Server Entwicklungsumgebung.

Gespeicherte Prozeduren

Im DBC werden nicht nur die erweiterten Definitionen für Tabellen und Felder, die benannten Verbindungen und die Definitionen der lokalen oder entfernten Ansichten abgelegt. Im DBC kann darüber hinaus jede benutzerdefinierte Prozedur oder Funktion hinterlegt werden, die immer dann zur Verfügung stehen muß, wenn die Datenbank geöffnet ist. Gespeicherte Prozeduren werden beim Öffnen der Datenbank automatisch in den Speicher geladen. Sie sollten sich deshalb auf Funktionen beschränken, die immer dann verfügbar sein müssen, wenn die Datenbank-Struktur oder die in der Datenbank gespeicherten Daten verändert werden. Dies trifft generell auf die Funktionen zu, die von den Ausdrücken aufgerufen werden, die die Gültigkeitsregeln für Tabellenfelder und Tabellensätze beschreiben. Die Funktionen, die von den UPDATE-, INSERT- und DELETE-Triggern aufgerufen werden, gehören ebenfalls als gespeicherte Prozeduren in die Datenbank. Verarbeitungsfunktionen Ihrer Anwendung sollten Sie jedoch nicht in der Datenbank speichern, da diese nur unnötig Hauptspeicherplatz belegen, wenn die Datenbank außerhalb Ihrer Anwendung geöffnet wird.

Pufferung, Satzsperre und Tabellensperrung

Wenn Sie in FoxPro 2.x ein Feld einer Datentabelle direkt mit einem Ein/Ausgabefeld einer Maske verknüpft haben, mussten Sie selbst dafür sorgen, daß der Inhalt des Datensatzes vor dem Ändern gesichert wurde, um dem Anwender die Möglichkeit zu geben, seine Änderungen rückgängig zu machen. Die weitaus meisten FoxPro-Programmierer haben deshalb darauf verzichtet, direkt auf den Tabellen der Datenbank zu arbeiten. Stattdessen wurde mit Hilfe des SCATTER Befehls der Inhalt des Datensatzes in Speichervariablen übertragen, die nach der Änderung mit GATHER zurück in den Datensatz geschrieben wurden. Diese Vorgehensweise hat sich erübrigt, da Visual FoxPro unterschiedliche Möglichkeiten der Pufferung anbietet. Die Felder einer gepufferten Tabelle können problemlos mit Ein-/Ausgabefeldern verknüpft werden. Jede Änderung, die der Anwender an den Daten vornimmt, landet nämlich zunächst im VFP-eigenen Puffer für diese Tabelle. Erst wenn über das Programm (oder das Befehlsfenster) der Befehl TABLEUPDATE() aufgerufen wird, werden die Daten in die Originaltabelle zurückgeschrieben. TABLEREVERT() verwirft alle Änderungen und aktualisiert den Puffer wieder mit den Originaldaten. Für jede Tabelle können Sie folgende Auswahl treffen:

Keine Pufferung: Verhalten wie in FoxPro 2.x

Pessimistische Datensatzsperre: Der Originaldatensatz wird gesperrt, bevor er in den Puffer übertragen wird. Dort kann er bearbeitet werden, ohne daß die Gefahr besteht, daß gleichzeitig ein anderer Benutzer Änderungen an diesem Datensatz vornimmt. Die Sperre erfolgt, sobald der Anwender beginnt, den Datensatz zu ändern. Wenn der Satz nicht gesperrt werden kann, gibt Visual FoxPro einen Hinweis aus. Ist der Satz gesperrt, können andere Anwender nach wie vor lesend darauf zugreifen. Sie erhalten jedoch immer die Daten im Ursprungszustand, das heißt ohne die Änderungen, die gerade an diesem Datensatz vorgenommen werden.

Optimistische Datensatzsperre: In diesem Fall wird der Originaldatensatz sofort in den Puffer geladen und kann dort verändert werden. Die Sperre erfolgt erst beim Speichern der geänderten Daten. Dadurch können Aktualisierungskonflikte entstehen, die durch eine Fehlerbehandlungsroutine abgehandelt werden müssen. Mit GETFLDSTATE() läßt sich feststellen, ob und welche Feldwerte verändert wurden und ob der Löschstatus des Satzes während der eigenen Bearbeitung verändert wurde. CURVAL() gibt den aktuellen Wert eines Feldes im Originaldatensatz an, während OLDVAL() den Wert des Feldes angibt, den es vor der eigenen Änderung hatte. Mit diesen Abfragen können Sie entscheiden, wie auf den Aktualisierungskonflikt zu reagieren ist. Abhängig von dieser Entscheidung können Sie die Aktualisierung entweder durch Setzen eines Zusatzparameters bei TABLEUPDATE() erzwingen oder Ihre eigenen Änderungen durch TABLEREVERT() verwerfen.

Pessimistische Tabellensperrung: Jeder Datensatz wird gesperrt, sobald er bearbeitet wird. Wenn der Satzzeiger auf einen anderen Satz verschoben wird, wird im Gegensatz zur pessimistischen Datensatzsperre keine Aktualisierung der Originaldaten vorgenommen. Der Satz bleibt weiterhin gesperrt, bis mit einem TABLEUPDATE() alle bearbeiteten Sätze aktualisiert werden.

Optimistische Tabellensperrung: Alle bearbeiteten Sätze bleiben im Puffer, bis mit einem TABLEUPDATE() die Aktualisierung versucht wird. Auch hierbei kann es zu Aktualisierungskonflikten kommen, wenn einer der Sätze zwischenzeitlich von einem anderen Benutzer verändert wurde. Die Aktualisierung wird durchgeführt, bis alle Sätze aktualisiert sind oder ein Aktualisierungskonflikt aufgetreten ist. Ein Aktualisierungskonflikt sollte auch hier mit einer entsprechenden Fehlerroutine behandelt werden. Mit der Funktion GETNEXTMODIFIED() können Sie vor der Aktualisierung prüfen, ob irgendwo ein Konflikt auftreten würde und diesen im Vorhinein abhandeln.

Transaktionen

Wenn während der Aktualisierung mehrerer Sätze ein Konflikt oder ein anderer Fehler auftritt, so sind die Originaldaten der vorher aktualisierten Sätze bereits geändert. Gesetzt den Fall, Ihre Fehlerbehandlungsroutine ermöglicht dem Anwender den Abbruch des Aktualisierungslaufs, oder kann aus anderen Gründen den Konflikt nicht lösen und die Aktualisierung fortsetzen, so enthält Ihre Datenbank im schlimmsten Fall einen inkonstistenten Datenbestand. Um das zu vermeiden, können Sie Ihre Aktualisierungsoperationen als Ganzes schützen, indem Sie diese als eine Transaktion durchführen. Dazu setzen Sie vor die Aktualisierungsoperationen den Befehl BEGIN TRANSACTION und schließen die Aktualisierungsoperationen mit einem END TRANSACTION ab. Wenn nun zwischendurch ein nicht behebbarer Fehler auftritt, können Sie alle bis dahin vorgenommenen Änderungen mit ROLLBACK rückgängig machen. Da Datenbankserver eigene Transaktionsschutzmechanismen haben, gelten diese Befehle nur für FoxPro eigene Tabellen. Im Umgang mit Datenbankservern können Sie entweder die automatischen Transaktionen verwenden (siehe dazu weiter oben „Client/Server Programmierung) oder mit SQLCOMMIT(), dem Pendant zu END TRANSACTION, die mit dem ersten SQL-Statement begonnene Transaktion abschließen. SQLROLLBACK() macht auch hier alle bis zu dem aufgetretenen Fehler durchgeführten Aktualisierungen wieder rückgängig.