Session D-STOR

ObjectSTORE Technologie - Realisation in VFP

Burkhard Stiller
Wizards & Builders GmbH


Motivation

Diese Session entstand aus der Notwendigkeit heraus, für das von mir projektierte Workflow Management System eine neue, dringend benötigte Speicherungstechnologie zu erstellen. Diese Speicherungsmethodik stellt die Basis eines generischen Object-Repository dar, in dessen Umgebung alle Business Objekte des neuen Workflow Management System zur Design- und Laufzeit verwaltet werden. Der hier vorgestellten Speicherungstechnik habe ich den Arbeitstitel ObjectSTORE-Technology gegeben.

Abstrakt

Vorhaben, die Eigenschaften von Speicherobjekten persistent in Tabellen ablegen zu können, kranken an der bekannten Tatsache, die landläufig als der Darstellungsbruch zwischen OO Programmierung und dem RDBM Konzept bezeichnet wird. Dabei wäre es in sehr vielen Anwendungsbereichen innerhalb der objektorientierten Programmierung äußerst vorteilhaft, wenn eine universelle, objektorientiert implementierte Technik verfügbare wäre, um damit die flexiblen Speicherstrukturen der instanziierten Programmobjekte zur Laufzeit auf ebenso flexiblen Tabellenstrukturen abzubilden.

Die neue Speicherungsmethode muß dabei genauso praxistauglich, wie mit vernünftigen Mitteln realisierbar sein! Dies bedeutet für die Implementation unter VFP 6.0, daß die Vorteile der Rushmore-optimierbaren Datenhaltung im systemeigenen relationalen Datenmodell keinesfalls über den Haufen geworfen werden sollen. Ganz im Gegenteil, gerade die Verwendung von relationalen Tabellen stellt die Herausforderung dar!

Sie  werden sehen, wie mit Hilfe eines linearisierten Speicherungsmodells durch die komplette Loslösung von Informationen über Daten (Metadaten) von den sie verwendenen Programmen, ein echter objektorientierter Übergang von OOP zum RDBMS auch unter VFP möglich wird.

Praxisbezug

Bevor ich die ObjectSTORE Technik nun im Detail erläutere, möchte ich kurz auf eine wichtige VFP Eigenheit eingehen, um Ihnen aufzuzeigen, wo ein zukünftiger Einsatz der ObjektSTORE Technik mit echtem Praxisnutzen liegen wird.

Wie Sie wissen, werden in der Entwicklungsumgebung von Visual FoxPro schon seit jeher fast alle variablen Anwendungsinformationen in Tabellen abgelegt. Reporte, Menüs, Projekte, Klassenbibliotheken, Datenbankkontainer und Forms sind auf der Festplatte nichts anderes als DBF-Dateien mit speziellen Dateinamenserweiterungen. Was auf der einen Seite vom versierten Profi durchaus als Segen einer offenen Systemarchitektur angesehen wird, bleibt auf der anderen Seite für jeden Neueinsteiger durch die scheinbar chaotische Tabellenvielfalt lange Zeit ein Buch mit sieben Siegeln.

Warum wurde bislang noch keine vereinheitlichte Tabellenstruktur entwickelt, die alle der oben aufgeführten VFP DBF-Dateitypen aufnehmen kann? Wäre es nicht auch für Sie schön, wenn Sie für jedes Kundenprojekt, nur noch eine einzige Tabelle öffnen müßten - und alle Informationen zu Menüs, Reporten usw. wären sofort verfügbar?

Leider verhindert die Verschiedenartigkeit der zu speichernden Informationen eine gemeinsame Ablage in der uns altbekannten Form. Eine solche, theoretisch vielleicht mögliche, generische Tabellenstruktur wäre in sich wiederum mit Sicherheit spaltenanzahlmäßig überladen und dadurch unlesbar, unwartbar und deshalb unbrauchbar. Also stellt vielleicht doch der VFP eigene Projektmanager mit seinem PJX-Tabellen Repository, das lediglich Referenzen auf andere Strukturen sammeln kann, das 'höchste der Gefühle' dar? Ich wage zu behaupten: NEIN!

Bedenken Sie nur einmal die Tatsache, daß VFP-Reporte immer noch nicht objektorientiert in Reportklassen angelegt werden können, so daß Ihr Anwender später für jede kleine Änderung ein Duplikat des von Ihnen gelieferten Report-Originals anlegen muß. Solche 'Lösungen' lassen Sie und mich doch wieder in die prozedurale Steinzeit zurückverfallen. Zu verdanken haben wir dies allerdings der derzeitig verwendeten Speicherungsform, nämlich der herkömmlichen Ablage in einer restriktiv strukturierten DBF.

Zudem, viele Einzeltabellen erschweren nicht nur den Überblick für den Entwickler, sondern bedeuten auch ein nicht zu unterschätzendes Fehlerpotential in Hinblick auf System- Anpassung, Wartung und Erweiterung.

Eine Speicherungstechnik, die es ermöglicht ALLE Aspekte (Daten, Objekte, Prozesse) einer Anwendung in einer zentralen DBF zu halten, ermöglicht somit erst den Aufbau von generischen Komponenten und deren fehlerfreie Ansteuerung.

Die im folgenden beschriebene ObjectStore Technologie erfüllt genau diese Anforderungen und läßt sich ganz besonders einfach, mit dennoch unglaublich hoher Leistungsfähigkeit, in Visual FoxPro realisieren!

Theorie

Einer der Hauptschwachpunkte eines jeden RDBMS und dessen Anbindung an die es nutzenden Anwendungsprogramme liegt in der noch unzureichenden Entkoppelung der Daten von den Anwendungsprogrammen!

Zwar sind die Daten selbst schon seit langer Zeit von den sie nutzenden Programmen physikalisch getrennt (da in Tabellen externalisiert abgelegt), die Informationen über die Daten (sprich also Metadaten) aber noch lange nicht! Sie erkennen dies sofort, wenn Sie sich einen x-beliebigen Quelltext ansieht, der direkt auf Daten einer Tabelle innerhalb eines RDBMS zugreift. Dort wird ein Datum der Tabelle beispielhaft in folgender Form adressiert:

THIS.MyProperty = DatabaseName!TableName.FieldName

Kennen Sie in diesem Beispiel also den Datenbank-, oder den Tabellen-, oder Feldnamen nicht, können Sie auch nicht auf das durch diesen Pointer adressierte Datum zugreifen.

Dies bedeutet aber gleichzeitig, daß Sie ganz entscheidende Informationen über die zu bearbeitenden Daten (Metadaten) in Ihren Anwendungsprogrammen programmtechnisch integrieren, bzw. voraussetzen.

Nun können schlaue Geister natürlich einwenden, daß dies sowieso nicht der richtige Weg sei, innerhalb einer Methode Felder einer Tabelle zu referenzieren. Richtiger scheint Ihnen folgende Vorgehensweise:

  1. Definition von protected Properties für Datenbank-, Tabellen- und Feldnamen
  2. Laden dieser Properties mit den konkreten Namen bei der Instanziierung der Klasse
  3. Zugriff auf die Daten innerhalb der Methoden der Klasse in folgender Art und Weise:
cReferenz = THIS.cDataBaseName + "!" +  THIS.cTableName + ; 
            "."+THIS.cFieldName 
THIS.MyProperty = EVAL(cReferenz) 

Dies ist natürlich richtig und nach dem heutigen "Stand der OOP-Technik" auch der Weg, wie unabhängige Komponenten derzeit aufgebaut werden.

Allerdings wird der von mir zu Beginn dieses Kapitels hervorgehobene Hauptschwachpunkt der Koppelung zwischen Daten in Tabellen und Properties in Objekten von Ihnen nicht behoben, sondern allenfalls verschoben, denn Sie verlagern die dringend benötigten Metadateninformationen lediglich innerhalb der implementierenden Klasse in eigens dafür reservierte Properties - und diese müssen explizit für jede Spalte der Tabellenstruktur angelegt werden, was nichts anderes bedeutet, daß Sie wiederum die Informationen über die eigentliche Struktur der benutzten Tabelle in den Properties Ihrer Klasse implementieren. An dieser Stelle soll gar nicht weiter ausgeführt werden, daß der Spalten- respektive der Property-Typ natürlich auch als bekannt vorausgesetzt und damit in der Klasse implementiert werden.

All diese Umstände zeigen deutlich, daß Sie auf die eine oder andere Weise immer Metadateninformationen über Ihre Tabellenstrukturen in Ihren Klassen verwenden, solange Sie die Daten in den Ihnen heute bekannten Formen adressieren. Damit wird die Programmierung von vollständig kapselbaren Datenobjektklassen (Datenobjektkomponenten) verhindert.

Der erste Schritt auf dem Weg zu einer erfolgversprechenden Abbildung von OOP-Speicherobjekten auf Tabellen einer Datenbank muß daher lauten:

Entkoppelung aller Daten einschließlich der Metadaten von den sie verwendenden Objektinstanzen (sprich Eigenschaften, Ereignisse und Methoden der Laufzeitobjekte) innerhalb der Programme.

Metadaten Information

Wenn Sie versuchen wollen, die Metadateninformationen die in Ihren Programmen verwendet werden, zu externalisieren, so müssen Sie im ersten Schritt feststellen, um welche Informationen es sich dabei handelt. Im zweiten Schritt müssen Sie dann ermitteln werden, wie diese Informationen im Detail benutzt werden.

VFP stellt Ihnen eine ganze Reihe von Funktionen zur Verfügung, mit deren Hilfe Metadateninformationen über Tabellen ermittelt und erzeugt werden können. Hier ein Auszug:

DISPLAY STRUCTURE .... 
DISPLAY TABLES ..... 
DISPLAY VIEWS .... 
DBF(), HEADER(), FCOUNT(), FIELD(), FSIZE(),  RECCOUNT() 
TAG(), CDX(), KEY(), FOR() 
TARGET(), RELATION() 

usw.

Sie erhalten u.a. Informationen über die Struktur einer Tabelle, deren Felder, Indizes und Relationen zu anderen Tabellen. Die Vielzahl der Funktionen, die Ihnen VFP zu diesem Aufgabengebiet zur Verfügung stellt, allein zeigt sehr deutlich, daß es eine schier endlose Menge an Metadateninformationen über Ihre Tabellen gibt, die Sie allesamt in Ihren Programmen nur über diese Funktionen abrufen können, da sie nicht offensichtlich sind und doch immer wieder dringend benötigt werden!

Ein ganz besonders nützlicher VFP-Befehl dieser Kategorie ist COPY STRUCTURE EXTENDED TO ....  da Ihnen diese Anweisung alle Felder einer Tabellenstruktur mitsamt den dazugehörigen Feldattributen mundgerecht in Tabellenform präsentiert. Die Art und Weise, wie Ihnen dieser Befehl die Metadateninformationen zurückgibt, wird später noch eingehend behandelt!

Pointer vs. Handles

Nachdem Sie nun eine ungefähre Vorstellung von den eigentlichen Metadateninformationen haben, betrachten Sie die Art und Weise, wie diese Informationen im Programm adressiert werden. Ich möchte das eingangs schon aufgeführte Beispiel in einer etwas konkreteren und verkürzten Form aufgreifen:

oKundenObject.KdnrProperty = Kunden.Kdnr 

Sie weisen hier dem Kundennummer-Property der Instanz eines beispielhaften Kundenadressenobjekts die Kundennummer aus einer irgendwo vorhandenen Kundenadressentabelle zu.

Links vom Zuweisungsoperator benutzen Sie ein Handle, rechts dagegen (trotz der gleichen Notation!) nur einen Pointer. Ein Handle ist bekanntlich ein Zeiger auf einen Zeiger, ein Pointer dagegen adressiert einen Wert im Speicher direkt. Es wäre nämlich nach der Anweisung SELECT Kunden in Ihrem Fall ohne weiters auch folgende Schreibweise möglich:

oKundenObject.KdnrProperty = Kdnr  

Der Teil  Kunden des Pointers  Kunden.Kdnr spezifiziert  dabei den Pointer  Kdnr lediglich genauer. Umgekehrt würde der Ausdruck  KdnrProperty = Kunden.Kdnr zwangsläufig einen Fehler verursachen! Zumindest solange, wie keine gleichnamige Speichervariable im Programm vorhanden ist.

Der Gebrauch eines Handles bringt natürlich intern einen erhöhten Verwaltungsaufwand mit sich. Diesen Preis muß man aber für eine höhere Flexibilität bezahlen. Wohlgemerkt, erst durch den Einsatz eines Handles lassen sich so schöne Dinge wie ein Bridge-Design-Pattern realisieren.

Also merken Sie sich auf der ToDo-Liste für die spätere Implementation an dieser Stelle vor:

Daten, die in Tabellen derzeit via Pointer angesprochen werden, sollten ab sofort durch die Benutzung von Handlen adressiert werden können.

Damit Sie im folgenden verstehen können, welche Aspekte des Darstellungsbruchs zwischen OOP und RDBMS in VFP mit Hilfe der ObjectSTORE Technik überwunden werden können, müssen Sie natürlich erst einmal wissen, was alles noch unter dem Darstellungsbruch in VFP konkret verstanden werden kann.

Data Dictionary vs. Class Library

Ich möchte an dieser Stelle einmal die Unterschiede zwischen Datentabellen und Klassenbibliotheken ausarbeiten.

Die Klassenbibliotheken enthalten, wie Ihnen bekannt ist, die Baupläne der zu instanziierenden Objekte. Der eigentliche Vorgang des Instanziieren wird von Visual FoxPro intern automatisch erledigt. Das heißt, Sie als Programmierer brauchen nicht erst umständlich Arbeitsspeicher im Datensegment zu reservieren, um darin dann das eigentliche Objekt anzulegen. Dies nimmt uns VFP in der Funktion CREATEOBJECT() ab. Ebenso werden Sie von VFP direkt mit einem Pointer auf den nun reservierten Adressraum versorgt.

So weit, so gut. Wie sieht das ganze nun auf der Tabellenseite aus?

Nun, hier liegen die Dinge deutlich anders. Obwohl Sie wissen, daß eine visuelle Klassenbibliothek in VFP auch als Tabelle abgelegt ist, dürfen Sie keinesfalls annehmen, daß damit eine Tabelle im RDBMS selbst auch eine Art Klasse darstellt - weit gefehlt! Wenn die physikalische Tabelle selbst eine Klasse darstellen würde, was müßte man dann unter der Instanz dieser Klasse verstehen?

Nun ich möchte Ihnen hier keine Rätselstunde zumuten, deshalb stelle ich an dieser Stelle zuerst die Ergebnisse meiner Ausarbeitungen vor, um diese dann im Detail zu diskutieren.

Wenn Sie für eine Klassenbibliothek von VFP eine Entsprechung auf der Tabellenseite (sprich im RDBMS) von VFP suchen, so werden Sie zwangsläufig ein Data Dictionary finden.

Genau wie die Klassenbibliotheken die Baupläne der zu instanziierenden Objekte enthalten, sind in einem Data Dictionary die Baupläne (Metadateninformationen) für die verwendeten Tabellen enthalten. Hierbei müssen Sie ganz besonders darauf achten, daß in einem Bauplan für eine Datenbanktabelle lediglich die Struktur der jeweiligen Tabelle beschrieben ist. Und natürlich auch alle anderen Metadateninformationen wie Indizes, Relationen etc. Die Tabelle, die mit Hilfe eines DDs generiert wird, ist eingangs (normalerweise) immer leer.

Erst wenn Sie Datensätze in die Tabelle INSERTen, erzeugen Sie per Definition Instanzen der eigentlichen Tabellenstruktur. Das heißt im Klartext:

Unter einer persistenten Instanz eines Datenobjekts versteht man einen einzelnen Datensatz innerhalb der Tabelle.

Im Gegensatz zur Erzeugung von Objekten im VFP Hauptspeicher aus den Klassenbibliotheken, können Objekte, d.h. Tabellenzeilen / Datensätze nicht direkt aus einem Data Dictionary heraus angelegt werden, denn es muß erst mit Hilfe der entsprechenden Tabellenstruktur eine konkrete Klasse (die physikalische Tabelle selbst) erzeugt werden.

Wie nun deutlich wird, muß beim Instanziieren von Datensätzen (also dem Einfügen eines neuen Records in eine Tabelle) im Gegensatz zur Instanziierung von Objekten aus VFP Klassen, ein Zwischenschritt gemacht werden. Dies ist der Schritt in dem eine neue, leere Tabelle angelegt werden muß.

Die Tabelle stellt also auf der einen Seite eigentlich nur eine Art virtuellen Arbeitsspeicher für die persistenten Objektinstanzen dar. Andererseits stellt die Struktur der Tabelle selbst (hier repräsentiert durch das Data Dictionary) auch die Klasse dar, aus der (und in der) die Datensatzobjekte ihrerseits instanziiert werden!

Die Struktur der Tabelle enthält nämlich einen großen Teil der Metadateninformationen, die im Tabellenkopf abgespeichert sind. Andere Metadateninformationen, die im Tabellenkopf keinen Platz mehr finden, da das althergebrachte DBF-Format nicht gänzlich umgekrempelt werden kann und soll, wurden früher im selbstgemachten Data Dictionary mitgespeichert, heute steht dafür die Datenbank (DBC) unter VFP zur Verfügung.

Ein weiterer wichtiger Unterschied zwischen beiden Welten ist die Tatsache, das sich Tabellen nicht einfach Subclassen lassen. Sie können jetzt zwar im Data Dictionary eine Art Vererbungshierarchie auf Strukturdefinitionsebene aufbauen, jede Objektinstanz, d.h. jeder daraus erzeugte Datensatz, müßte aber in einer eigenen Tabelle abgelegt werden. Ein Subclassen im Anwendungsprogramm in der denkbaren Form DEFINE MyCustTable AS CustomerBaseTable wäre zudem ein recht umständliches Unterfangen, das ich an dieser Stelle gar nicht erst weiter ausleuchten möchte.

Die Ablage unterschiedlich strukturierter Sätze innerhalb einer einzelnen Tabelle ist also im VFP‘s DBF‑Ablageformat wie wir es seit jeher kennen nicht möglich.

Im nächsten Kapitel werde ich Ihnen einen neuartigen Denkansatz und einen dazu passenden, gar nicht mehr so neuen, Lösungsansatz für die bislang beschriebene Problematik präsentieren. Abschließend hier noch einmal die Zusammenfassung der wichtigsten Thesen meiner theoretischen Vorüberlegungen:

  • Entkoppelung aller Daten einschließlich der Metadaten von den sie verwendenden Objektinstanzen (sprich Eigenschaften, Ereignisse und Methoden der Laufzeitobjekte) innerhalb der Programme.
  • Externalisierte Metadateninformationen sollten ebenfalls in Form von normalen VFP-Tabellen gespeichert werden, um die VFP-internen leistungsstarken Datenzugriffsfunktionen nutzen zu können.
  • Daten in Tabellen sollten nicht via Pointer sondern müßten durch Handle in den Methoden der Verarbeitungsprogramme adressiert werden können.
  • Entsprechung für OOP-Klassenbibliotheken auf Seite der RDBMS werden in Form eines Data Dictionary ausgeführt

Überwindung des scheinbaren Widerstands

Ein Datensatz hat innerhalb einer Tabelle immer die gleiche Struktur (bis aufs i-Tüpfelchen genau gleich)! Das bedeutet:

  • Ein Feld einer bestimmten Spalte hat in allen Datensätzen der Tabelle immer den gleichen Namen!
  • Ein Feld einer bestimmten Spalte hat in allen Datensätzen der Tabelle immer die gleiche Länge!
  • Ein Feld einer bestimmten Spalte hat in allen Datensätzen ..... usw.

Sie kennen den Rest der Aufzählung. Nichts ist hieran variabel. Vor allen Dingen ist es z.B. nicht möglich, in einem Datensatz die Spaltenanzahl zu variieren.

Die Lösung dieses 'Speicherproblems' ist gleichermaßen flexibel, wie erschreckend einfach und heißt:

Linearisierung der Daten in standardisierten Tabellenstrukturen.

Lösungsansatz

Ich habe einfach einmal darüber nachgedacht (als eine Art Normalisierung der Konstruktion), wie es sich machen würde, für jedes "Table Object Property" einen eigenen beschreibenden Datensatz anzulegen.

Zur Hilfe kamen mir bei der Knobelei die Konstruktionen, die ich selbst schon in eigenen FPW-2.6er Programmen für das Audit-Trailing eingesetzt hatte. Wenn man eine Feldwert-Änderungsverfolgung implementiert, die nur eine Tabelle verwendet, muß man in dieser Auditing-Tabelle für jedes Feld eines Datensatzes, das geändert wurde, einen eigenen Audit-Datensatz anlegen. So nach dem Motto Feld xyz, Wert vorher, Wert nachher usw.

Implementation

In einem linearen Speicherungsmodell gibt es nur Tabellen mit einer Standardstruktur. Um es eingangs nicht zu kompliziert zu machen, nehmen Sie im ersten Beispiel einmal an, daß Sie nur String-Daten speichern wollen.

Diese neue Standardstruktur der Tabelle sieht wie folgt aus:
Spaltenname Länge Nachkomma Typ Zweck

cDescriptor

25

0

C

enthält den Feldnamen (Descriptor)

cDatum

200

0

C

Feldwert (der Nutztext)

cDynaSetID

20

0

C

Gleiche Kennung faßt zu logischem Satz zusammen

Die Struktur dieser Tabelle ist banal, wie ich meine. Schauen wir uns an, wie Datensätze einer "normalen" VFP-Tabelle hierin abgelegt werden können. Hier die Inhalte einer normalen Adressentabelle:

Anrede Firma Name Nachname Ort Telefon

Firma

Meier

Müller

Eppingen

1234

Herr

Franz

Meier

Köln

4567

Firma

1234

Anlagenbau

GmbH

Stuttgart

5678

Tabelle 1.) Standard Adressentabelle

Diese Datensätze werden nun linearisiert abgelegt:

cDescriptor Cdatum           cdynaSetID

Anrede

Firma

0000000001

Firma

Meier

0000000001

Name

0000000001

Nachname

Müller

0000000001

Ort

Eppingen

0000000001

Telefon

1234

0000000001

Anrede

Herr

0000000002

Firma

0000000002

Name

Franz

0000000002

Nachname

Meier

0000000002

Ort

Köln

0000000002

Telefon

4567

0000000002

Anrede

Firma

0000000003

Firma

1234

0000000003

Name

Anlagenbau

0000000003

Nachname

GmbH

0000000003

Ort

Stuttgart

0000000003

Telefon

5678

0000000003

Tabelle 2.) Adressen in linearer Ablageform

Über die gemeinsame DynaSetID werden die einzelnen Sätze zu einem logischen Satz zusammengefaßt. Diesen logischen Datensatz nenne ich ab jetzt einen DynaSet. Den einzelnen Sub-Record des DynaSets nenne ich DynaField.

Interessante Effekte gefällig?

  • Ich kann z.B. eine Anfrage an die Tabelle richten: "Zeige mir alles zum Wert <1234>"! Als Resultat bekomme ich umgangssprachlich formuliert bei obiger Tabelle: "Gefunden wurde eine Firma und eine Telefonnummer mit diesem Wert. Sollen wir diese Datensätze anzeigen?" Dies wird jetzt möglich, da ALLE abgebildeten Spaltenwerte linear vorliegen und ich quasi alle virtuellen Spalten nacheinander durchsuche.
  • In dieser linearisierten Speicherung kann jetzt aber auch der Feldname (Descriptor) in jedem Datensatz anders heißen! Da ich jetzt auch die Spalte cDescriptor durchsuchen kann, nach dem Motto: "Zeige mir alle Telefonnummern", wird der Descriptor (Feldname) jetzt selbst zum Datum.
  • Jeder Datensatz kann ab sofort eine andere Struktur haben! Ich habe die Möglichkeit, logische Datensätze nach Bedarf in einem Satz aus Tausenden von Feldern aufzubauen, in einem anderen Fall besteht der Satz dann z.B. nur aus einem Feld!

Ein Praxisnutzen:
E
in Anwender, der in einem Sonderfall für seine Zwecke ein zusätzliches Feld benötigt, braucht nicht mehr zum Datenbankadministrator gehen, um ihn zu bitten die Struktur der Basistabelle für ihn zu erweitern, oder eine weitere Zusatztabelle zu erstellen und relational zu verknüpfen! Er fügt einfach eine einzelne Zeile mit gleicher DynaSetID zusätzlich ein und schon hat sein Datensatz die gewünschte Strukturerweiterung!

Der größte Nutzen scheint mir aber, daß wir dieses lineare Tabellenmodell auf das VFP eigene Objektmodell übertragen können.

Dies setzt natürlich die Programmierung von entsprechenden Objektmethoden voraus, damit die lineare Anordnung der einzelnen Datumswerte ein und ausgelesen werden kann. Nichtsdestotrotz können jetzt Properties des Datenobjekts dynamisch zur Laufzeit hinzugefügt werden, da der Vorgang der Speicherung der Object-Properties in die Tabelle OHNE Zugriff und Kenntnis von Datendefinitionen auskommt. Ebenso können Object-Properties beim Einlesen des logischen Datensatzes dynamisch erzeugt werden.

Verfeinern Sie an dieser Stelle nun das lineare Speicherkonzept und ergänzen Sie die Struktur der Tabelle um einige Felder:

Spaltenname Länge Nachk Typ Zweck

cDatum

254

0

C

Feldinhalt für Character-Werte

YDatum

8

0

Y

Feldinhalt für Currency-Werte

DDatum

8

0

D

Feldinhalt für Date-Werte

TDatum

8

0

T

Feldinhalt für DateTime-Werte

BDatum

8

0

B

Feldinhalt für Double-Werte

FDatum

8

0

F

Feldinhalt für Float-Werte

LDatum

1

0

L

Feldinhalt für Logical-Werte

NDatum

20

Variant

Z (N)

Feldinhalt für Numeric-Werte (werden intern als Z-Typ abgelegt)

IDatum

4

0

I

Feldinhalt für Integer-Werte

ODatum

4

0

O

Feldinhalt für OLE-Objekt-Pointer

MDatum

4

0

M

Feldinhalt für Pointer auf normale Memo-Felder

ADatum

4

0

M

Feldinhalt für Pointer auf Memo-Felder die Arrays enthalten

XDatum

4

0

X (M)

Feldinhalt für Pointer auf binäre Memo-Felder

ZDatum

254

0

Z (C)

Feldinhalt für Character-Werte (binäre Character-Werte)

CDescriptor

128

0

C

enthält den Feldnamen (Descriptor)

CProtection

1

0

C

L:=Local, G:=Public, P:=Protected, H:=Hidden

CTimestamp

20

0

Z

Timestamp des Feldes der letzten Änderung (binär codiert)

CdynaSetID

20

0

Z

Gleiche Kennung faßt zu logischen Sätzen zusammen (DynaSets)

CfieldID

4

0

B

Feldtyp, User-Info, App-Info (binär codiert in Double-Wert)

CaccessLevel

4

0

B

User-Zugriffslevel (binär codiert in Double-Wert)

CpreferredTyp

1

0

Z

Enthält den bevorzugt zu verwendenen Feldtyp

CotherTyps

14

0

Z

Enthält Kennungen der zusätzlich verwendeten Feldtypen

Tabelle 3.) Verbesserte Struktur der Tabelle

Wie man hier sieht habe ich für jeden denkbaren Datentyp nun eine eigene Spalte angelegt. Die Spalte "cPreferredTyp" enthält die Typkennung des gespeicherten Datums, das ich primär verwenden will.

Beispiel: Ich habe eine Postleitzahl und möchte diese primär als numerischen Wert speichern. Also ist in diesem DynaSet <cPreferredTyp = "n">. Ein denkbarer Suchzugriff kann nun wie folgt formuliert werden:

SELECT (cPreferredTyp+"Datum"), cDescriptor,  cDynaSetID ; 
FROM <tablename> ; 
WHERE cDescriptor LIKE "Postleitzahl" 

Natürlich wird es durch unsere neuartige Konstruktion auch möglich, diese Postleitzahl in einem anderen Format, z.B. als String oder gar in einem Memo-Feld im gleichen Sub-Record des DynaSets abzulegen. Dann wird die Spalte "cOtherTypes" mit den zusätzlich verwendeten Typkennungen des gespeicherten Datums belegt. Hier würde z.B.: < cOtherTypes = "cmx"> bedeuten, daß das Datum "Postleitzahl" zusätzlich in den Spalten cDatum, mDatum und xDatum eingetragen wurde und somit zur Verfügung steht. Damit werden Typkonvertierungen "on the fly" möglich, ohne das alte Datum zu vernichten! Dieser Umstand ist dann ganz besonders wichtig, wenn mehrere ältere Versionen einer Anwendung zusammen mit dem aktuellen Update parallel im Netz betrieben sollen.

Wie Sie unschwer erkennen können, wurden von mir einige weitere Informationsfelder eingefügt. So können Sie im Timestamp-Feld den Zeitpunkt des letzten Zugriffs ablegen usw. Gehen wir aber jetzt zurück zur "Normalisierung" eines solchen Ablagemodells.

Wie sieht es z.B. mit relationalen Verbindungen zwischen den einzelnen DynaSets aus?

Nun, Relationen können über den variablen Descriptor hergestellt werden! Nehmen wir an, wir haben 1000 DynaSets, die von einem Datenobjekt "Lieferant" angelegt worden sind und die Adressen usw. des Lieferanten enthalten.

Ein Sub-Record eines solchen DynaSets hätte dann im Descriptor-Feld den Inhalt (den Wert / das Datum) "Kennummer des Lieferanten" und z.B. den Wert "12345" im iDatum-Feld stehen.

Andere DynaSets unserer Datenbank beschreiben nun unsere Artikel. Ein Sub-Record eines solchen Artikel-DynaSets hätte dann in einem Descriptor-Feld z.B. den Inhalt (den Wert / das Datum) "Wird geliefert von Lieferant mit Kennummer" und im iDatum-Feld den erforderlichen Wert "12345" stehen.

Jetzt ist doch alles ganz einfach. Ich ermittle meine Lieferanten:

SELECT DISTINCT cDynaSetID, cDescriptor FROM  <tabellen name>; 
   WHERE cDescriptor = "Kennummer des  Lieferanten" ; 
   INTO <sonstwohin> 

Jetzt stehen mir alle DynaSetIDs zur Verfügung, mit deren Hilfe ich eine Auswahlliste von Lieferanten erzeugen kann (Dies funktioniert natürlich in einem Arbeitsgang mit Hilfe eines Sub-Select).

Nachdem der Anwender in einem Dialog einen Lieferanten anwählt, sollen alle Artikel, die dieser Lieferant mir liefert angezeigt werden:

SELECT DISTINCT cDynaSetID, cDescriptor,  iDatum FROM <tabellen name>; 
   WHERE (cDescriptor = " Wird geliefert von  Lieferant mit Kennummer " ; 
   AND iDatum = oDataObjekt.FieldProperty);   // Objekt enthält Lieferantennummer "12345" 
   INTO <sonstwohin> 

Mit diesen Artikel-DynasetID listen wir nun die entsprechenden Artikel auf, fertig.

Und jetzt zu der Frage: Braucht man mit einer solchen objektorientierten, linearen Speicherung überhaupt noch ein Data-Dictionary? Macht das in den uns bekannten Ausprägungen überhaupt noch Sinn? Was wollen wir denn da verwalten? Das Chaos, das durch die enorme Flexibilität jetzt entsteht?

Eine Antwort ist: Ein Data-Dictionary im herkömmlichen Sinn enthält ja nur Metadaten, also genau die Informationen, die wir nun in unserem Speicherungskonzept direkt in den Tabellen verwalten. Allerdings liegt es aus anderen Gründen nahe, eine andere Art Data-Dictionary neu zu konzipieren. Die Methodik die dahinter steht, ist wie bisher immer dieselbe und lautet:

Alle Informationen über die Daten (Metadaten) direkt immer in den Datentabellen selbst mit ablegen!

Und so komme ich letztendlich zu einem einfachen Ergebnis:

Eine Datenbank, die auf meinem Konzept der linearisierten Datenspeicherung beruht besteht aus (mindestens) drei Tabellen, die in einem DBC eingebunden werden! Ich nenne diese Datenbank
Data-Object-Repository (DOR)

Die Aufteilung aller Dateninformationen auf drei Tabellen vereinfacht die Handhabung der Daten und deren Integration in VFP erheblich.

Sehen Sie sich die Aufteilung von "unten nach oben" kurz an:

Die DynaSet-Container Tabelle entspricht in etwa der bereits ausführlich beschriebenen Tabelle und enthält die DynaFields (Sub-Records), aus denen jeder einzelne logische Datensatz (DynaSet) besteht.

Da Sie aber ganz gehörige Schwierigkeiten bekommen würden, wenn Sie mit einem normalen VFP Report z.B. eine Listing für bestimmte DynaSets generieren wollten, wurde eine Art Record-Header-Tabelle darüber gelegt:

Die Tabelle "DynaSet Enumeration Table" enthält für jeden logischen Datensatz im DynaSet Container einen regulär aufgebauten Datensatz. Hier können dann weitere Metadaten gespeichert sein, also Informationen über die Daten des eigentlichen DynaSets. Welche Informationen sind hier denkbar:

  • Die DynaSet ID
  • Die Anzahl der Felder (Datensätze) des referenzierten DynaSets
  • Zugriffsrechte auf DynaSet-Ebene
  • Zuordnung des DynaSets zu Kollektionen (siehe weiter unten), zu Usern usw.
  • Eine Timestamp, wann der DynaSet angelegt wurde, eine für das letzte Änderungsdatum usw.
  • Eine User ID, wer den DynaSet angelegt hat, wer zuletzt geändert hat

... also alle Informationen, die für die allgemeine Beschreibung des DynaSets notwendig und interessant erscheinen. Mit Hilfe dieser Tabelle kann dann auch das eben angesprochene Reporting-Problem umschifft werden!

Der DynaSet Enumeration Table zählt also einerseits alle vorhandenen logischen Datensätze im DynaSet-Container Table auf, andererseits zählt er alle Properties eines DynaSets auf, die global für diesen DynaSet gelten.

Nun haben wir noch das Problem, die Gesamtheit aller Properties aller DynaSets irgendwo zu verwalten. Es kann natürlich durchaus erwünscht sein, daß jeder Anwender seine eigenen Data-Properties einem DynaSet zufügen kann, um z.B. nur für ihn wichtige Zusatzinformationen abzuspeichern. Allerdings müssen z.B. die meisten Descriptoren-Bezeichnungen, auf denen ja bekanntlich später die "virtuellen Relationen" aufgebaut werden müssen, irgendwo festgehalten werden. Es darf nicht vorkommen, daß irgendeine Subklasse einer Daten-Objekt-Klasse später plötzlich einen Ausdruck in den Descriptor der z.B. eine Lieferantenkennummer darstellt einträgt, der vom vorgegebenen Standardwert abweicht. Damit würde dieser Descriptor wertlos werden.

Solche Descriptoren-Enumerationen, Relationale Abhängigkeiten von DynaSets, sowie (ganz wichtig) die Definition von "Logischen Tabellen" innerhalb des DynaSet-Container werden im DynaSet-Collection-Definitions Table abgelegt und verwaltet. Auch hier kommt wieder eine ganz normale Tabelle zum Einsatz

Die DynaSet-Collection Definition Tabelle speichert z.B. folgende Informationen

Information Beschreibung

Namen von DynaSet Kollektionen

Einfach ausgedrückt: Tabellennamen von (logischen) Tabellen.

Tabellen-Header-Informationen dieser DynaSet Kollektionen, als da wären:

DynaSet_Collection_Reccount

Anzahl der DynaSets in der "Tabelle" (Kollektion)

DynaSet_Collection_Deleted_Reccount

Anzahl der derzeit als gelöscht markierten DynaSets

DynaSet_Collection_Min_Access_Right

Minimale Zugriffsberechtigung: Öffnen des DynaSets

DynaSet_Collection_Min_Append_Right

Minimale Zugriffsberechtigung: Anfügen von DynaSets

DynaSet_Collection_Min_Delete_Right

Minimale Zugriffsberechtigung: Löschen von DynaSets

DynaSet_Collection_Min_Zap_Right

Minimale Zugriffsberechtigung: Leeren aller DynaSets

DynaSet_Collection_Min_Export_Right

Minimale Zugriffsberechtigung: Exportieren der DynaSets

DynaSet_Collection_Min_Modify_Right

Minimale Zugriffsberechtigung: Bearbeiten von DynaSets

DynaSet_Collection_Min_Report_Right

Minimale Zugriffsberechtigung: Ausdrucken von DynaSets

DynaSet_Collection_Next_DynaSet_ID

Nächste DynaSet-Id die bei Neuanlage vergeben wird

Und viele Dinge mehr....

Für und Wider

Diese beschriebene Technik birgt natürlich nicht nur Vorteile mit sich. Ein graviereder Nachteil ist der sprunghaft ansteigende Platzverbrauch auf der Festplatte, wenn man alle möglichen Datentypen in nur einer Tabelle speichert. Dann halten Sie wirklich etliche (leere) Daten redundant vor. Ein Ausweg wäre die Aufteilung der einzelnen DynaField-Records nach Typ unterschieden auf verschiedene Tabellen, in denen nur dann ein Satz angelegt wird, wenn auch wirklich ein Datum abzuspeichern ist (genau wie im Memo-Feld).

Ein weiterer Nachteil ist der zu erwartende Performance-Verlust, der durch das Einlesen der DynaField-Records zwangsläufig entsteht wird. Der hier vorgestellte Ansatz soll auch primär nicht dazu verwendet werden riesengroße Datenmengen zu verwalten! Ganz im Gegenteil, die absolut flexible Speicherung überschaubarer Datenmengen ist Hauptzweck dieser Technik.

Vorteile gibt es, wie bereits zum Teil beschrieben, genug! Die Tatsache allein, Datensätze mit beliebigen Strukturen innerhalb einer logischen Tabelle abspeichern zu können, ist für mich Gewinn genug. Tatsächlich lassen sich jetzt noch weitere, nicht weniger interessante Dinge realisieren. Wie wäre es mit neuen Datentypen? Ein sogenannter User-Data-Typ könnte z.B. heißen: "WinWord_8.0_DocReferenz". Diesem Datentyp können jetzt Aktionen zugeordnet werden, wie z.B. "Dokument_verschlagworten()", "Dokument_weiterleiten()" usw. Ebenso werden jetzt Datentypen wie "Application-WorkFlow-Step", " Application-WorkFlow-SubProcess" usw. möglich! VFP's speicherbasierte Objekte (Instanzen von Klassen) können nun persistent auf DynaSets abgebildet und gespeichert werden!

Da die Speicherung der einzelnen Daten (und der Zugriff darauf) immer noch auf Basis von ganz normalen Tabellen geschieht, sollte es auch ohne weiteres möglich sein, das System auf z.B. MS SQL-Server oder andere C/S - Plattformen zu übertragen. Das heißt, im Anbetracht der angestrebten Realisation einer offenen RAD-Gesamtlösung, dürfte auch die Möglichkeit der Erzeugung von C/S-fähigen Anwendungen durch diese Speicherungstechnik nicht beeinträchtigt werden.

Zusammenfassung

Der vorgestellte Lösungsansatz einer Datenspeicherung Deskriptiv Sequenzialisiertes Relationales Datenmodell (DSRD) kann den viel zitierten Scheinwiderstand zwischen OOP und RDBMS überwinden. Das vorgestellte DSRD eignet sich vielleicht weniger für die Speicherung von sehr großen Datenmengen. Durch die absolute Flexibilität und die Abbildbarkeit und Speichrungsmöglichkeit von persistenten Objekten stellt es aber eine echte Alternative für die Realisation von bestimmten Speziallösungen dar.