Die Eigenschaft Data
eines Bausteins bietet sämtliche Methoden und Eigenschaften für den Zugriff auf und die Manipulation von Datenobjekten, seien dies lokale Objekte, Parameterobjekte oder globale Objekte. Falls notwendig, kann über die schreibgeschützte Eigenschaft Parent
auf den definierenden, aktiven Baustein zurückgegriffen werden.
Für die globalen Objekte und Parameterobjekte ist eine Eigenschaft mit dem Namen des jeweiligen Objektes verfügbar. Erneut bietet IntelliSense® von Visual Studio® Unterstützung beim Auffinden des benötigten Objektes:
Das jeweilige Objekt ist bereits passend zur definierten Datenstruktur verfügbar. Ebenfalls wird unterschieden zwischen Mengenobjekten1) und Einzelobjekten2). Details zur allgemeinen Struktur von und Umgang mit Datenobjekten sind getrennt beschrieben.
Wenn bei der Generierung der Assembly Dateninterfaces 3) angegeben sind, werden diese bei der Erstellung des Moduls berücksichtigt, indem alle Dateninterfaces, die das Modul implementiert, an Data
definiert werden.
Ein generiertes Dateninterface könnte z.B. so aussehen:
public interface IVertragsdaten: IMitf<IVertragsdaten> { ObjList<Aalvb0> Lvvbstn { get; } ObjList<Aalvv0> Lvvtg { get; } ObjList<Aalvt0> Lvvtgt { get; } }
… und seine Verwendung an Data
so:
public sealed class PgEfunVertrag_Data: ModuleData<PgEfunVertrag>, IVertragsdaten
In der Implementierung des Moduls können dann ExtensionMethoden, die für das Dateninterface implementiert sind, verwendet werden:
static class VertragsdatenInterface { public static bool IsVertragGueltig(this IVertragsdaten vertragsdaten, ulong versNr) { if (vertragsdaten.Lvvtg.Count == 0 || vertragsdaten.Lvvtgt.Count == 0 || vertragsdaten.Lvvbstn.Count == 0) { return false; } ... return true; } }
this.Data.IstVertragGueltig(VersNr);
public static ObjList<T> NewList(DataStructure<T> type); public static ObjList<T> NewList(DataStructure<T> type, String name); public static ObjList<T> NewList(DataStructure<T> type, String name, out bool created); public static T New(DataStructure<T> type); public static T New(DataStructure<T> type, String name); public static T New(DataStructure<T> type, String name, out bool created);
Zum Anlegen von lokalen Objekten steht an der Eigenschaft Data
die Methode New
(für Einzelobjekte) oder NewList
(für Mengenobjekte) zur Verfügung. Mittels IntelliSense® ist auch hier Unterstützung verfügbar:
Bei der Deklaration von lokalen Datenobjekten muss nicht zwingend ein Name angegeben werden. Falls der Name fehlt, wird intern ein eindeutiger Name vergeben. Wenn jedoch ein Name angegeben wurde, wird nicht notwendigerweise ein neues Objekt angelegt: Falls bereits ein Objekt mit diesem Namen existiert, wird dieses Objekt geliefert, insofern das Objekt von der Struktur zu den Deklarationsangaben4) passt. Um zu prüfen, ob das Objekt in der Tat neu angelegt wurde oder ein bereits existierendes Objekt benutzt wurde, kann optional ein out
Argument created
mitgegeben werden, aus dem dann nach dem Aufruf ableitbar ist, ob das Objekt hier erstmals für den aktuellen Baustein angelegt wurde. Sollte ein Objekt mit dem gleichen Namen existieren, das jedoch strukturell nicht passt, wird das mit einer Condition ZZTAAOM1::23
quittiert.
Es können nur Objekte von den in der Basis-Assembly generierten Datenstrukturen angelegt werden. In der Basis-Assembly werden automatisch alle Datenstrukturen angelegt, die für die in der Assembly befindlichen Bausteine als Schnittstellendaten oder Übergabeargumente für andere Bausteinen benötigt werden. Für den seltenen Fall, dass darüber hinausgehende Datenstrukturen benötigt werden, können diese ggf. bei der Generierung der Assembly explizit angegeben werden.
Dadurch, dass alle Methoden und Eigenschaften auf expliziten Typen basieren, können die Listen- und Datenelemente mit IntelliSense®-Unterstützung direkt benutzt werden:
public static ObjList<T> NewCachedList(DataStructure<T> type, Action<T> initializer); public static ObjList<T> NewCachedList (DataStructure<T> type, Action<ObjList<T>> initializer, String name); public static T NewCached (DataStructure<T> type, Action<T> initializer); public static T NewCached (DataStructure<T> type, , Action<T> initializer, String name);
Mit den Methoden Data.NewCached
(für Einzelobjekte) bzw. Data.NewCachedList
(für Listenobjekte)5) können Objekte angelegt werden, deren Inhalt bei der Ausführung des Moduls gecached wird. Wenn beim Anlegen des Objekts im Modul der Cache noch keine Daten für das Objekt enthält, wird der Initializer ausgeführt und werden die Daten in den Cache übernommen. Andernfalls wird das Objekt aus dem Cache bestückt.
Diese Vorgehensweise ist sinnvoll für Objekte mit gleichbleibendem, anwendungsunabhängigem Inhalt, der aufwändig abgeleitet werden muss, z.B. durch Zusammenführen mehrerer Schlüsseltabellen.
Die Angabe eines Namens ist optional. Die in Intellisense sichtbaren Argumente line
und file
werden automatisch bestückt und sollten nicht angegeben werden.
Der Cache wird von der Infrastruktur verwaltet und ist Gevo-übergreifend, d.h. dieselben Moduldaten können unterschiedlichen Gevos zur Verfügung gestellt werden. Die Lebensdauer des Cache ist undefiniert; die Anwendung kann nicht wissen, ob die Daten aus dem Initializer oder aus dem Cache bestückt werden. Da es sich um ein lokales Objekt handelt, wird der Inhalt bei einer Gevo-Unterbrechung nicht mit einem Kontext gesichert, sondern bei Wiederaufnahme neu aus dem dann vorliegenden Cache oder dem Initializer bestückt.
Es werden nur die Werte gecached, die im Initializer bestückt wurden. Änderungen an den Objektdaten im weiteren Verlauf des Moduls werden nicht in den Cache übernommen.
Der Initializer ist eine Methode vom Typ Action<T>
, wobei <T>
die Klasse des Objekts ist, also für Listenobjekte z.B. ObjList<SnXyz1>
, für SnXyz1
für Einzelobjekte:
static void initRec(SnlXyz1 rec) {…} static void initLst(ObjList<SnlXyz1> lst) {…}
Um den Inhalt eines BLOBs verarbeiten zu können, besteht die Möglichkeit, eine Datenstruktur auf ein Datenelement vom Typ BLOB (Binary Large OBject) anzuwenden, um ein neues, temporäres Objekt zu erzeugen:
var blobStructured = this.Data.Redefined(objSource, fieldInfo, structureTarget);
Dabei ist objSource
das Datenobjekt resp. der Datensatz, aus dem ein bestimmtes BLOB-Element verwendet werden soll, fieldInfo
die Definition des BLOB-Feldes, dessen Inhalt strukturiert benutzt werden soll, und structureTarget
die Datenstruktur, die für die Strukturierung des BLOB-Inhaltes angewandt werden soll. Um sicherzustellen, dass
fieldInfo
zur Struktur des objSource
passt, undfieldInfo
ein BLOB-Element beschreibt, undstructureTarget
ist
sieht die wirkliche generische Definition der Redefined
-Methode etwas komplizierter aus:
public TTgt Redefined<TObj, TDef, TTgt, TDefTgt, TFld>(DataObject<TObj> obj, FieldInfo<TObj, TDef, byte[], TFld> fld, DataStructure<TTgt, TDefTgt> tgtStructure, [UInt32 idx1], [UInt32 idx2], [UInt32 idx3]) where TObj : DataObject<TObj> where TDef : DataStructure<TObj, TDef> where TTgt : DataObject<TTgt> where TDefTgt : DataStructure<TTgt, TDefTgt> where TFld : FieldInfo<TObj, TDef, byte[], TFld>;
In der Praxis merkt man von dieser Komplexität jedoch nichts, und wird einem eher bei der Eingabe der Methode über IntelliSense Unterstützung geboten:
var vorBlob001 = this.Data.Redefined(objCtx, CtxdbRecord.Definition.Elements.Ar1VorBlob, CtxdbVorBlobVar001.Definition);
Die Rolle des Objektes entspricht der des Ursprungsobjektes in dem aktuellen Baustein. Wenn also das Ausgangsobjekt im aktuellen Baustein nur gelesen werden darf, ist auch das strukturierte BLOB-Elementobjekt ReadOnly
.
Alle Daten in dem temporären Objekt entsprechen jederzeit dem aktuellen Inhalt des BLOB-Elementes. Das temporäre Datenobjekt ist also ein direktes Fenster auf die BLOB-Daten und keine Kopie.
Alle Änderungen (insofern zulässig) werden direkt auf die binären Daten im BLOB-Element ausgeführt. Umgekehrt ändert sich auch der Inhalt des temporären Objekts, wenn im Ursprungsobjekt der Blob neu bestückt wird. 6)
Ausnahme: Wenn der Blob mit null
bestückt wird, wird an dem temporären Objekt ein leerer Datensatz angelegt. Änderungen an diesem Datensatz haben keine Wirkung auf das Ursprungsobjekt! Wenn anschließend das BLOB-Feld mit einer neuen Adresse bestückt wird, werden diese Daten wieder in das temproräre Objekt übernommen.
In der für das temporäre Objekt anzuwendenden Datenstruktur kann auch wiederum ein BLOB-Element vorhanden sein, welches ebenfalls mit einem (neuen) temporären Objekt verknüpft werden kann.7) Solche mehrstufigen Verknüpfungen sind jedoch mit großer Vorsicht zu verwenden, wenn während der Bearbeitung die BLOBS neu bestückt oder deren Inhalte geändert werden, da sich alle Änderungen auf allen Verknüpfungsebenen auswirken.
Die Infrastruktur erhält die Verknüpfung zwischen einem Blob und dem temporären Objekt so lange aufrecht, wie sowohl das temporäre Objekt als auch das Ursprungsobjekt bzw. der Satz aus dem Ursprungsobjekt existieren.
Es ist deshalb zu empfehlen, das temporäre Objekt freizugeben, sobald es nicht mehr benötigt wird. Dies kann z.B. durch Verwendung von Using
erreicht werden:
using (var blobredef = this.Data.Redefined(objCtx, CtxdbRecord.Definition.Elements.Ar1VorBlob, CtxdbVorBlobVar001.Definition)) { // ... }
Folgende Ausnahmen können auftreten:
Zztaaom1.UnexpectedFieldType
- das zu benutzende Feld ist zwar binär, aber kein BLOBZztaaom1.InvalidObjectHandle
- das zu benutzende Ausgangsobjekt ist entweder leer, ungültig oder ist ein Mengenobjekt und hat kein aktuell positioniertes ElementZztaaom1.MismatchStructure
- die Daten im BLOB-Element reichen nicht aus, um ein Objekt mit den notwendigen Daten zu bestücken
Alternativ kann auch ein temporäres Objekt als Kopie auf binären Daten erzeugt werden. Dies kann nützlich sein, wenn die binären Daten nicht aus einem BLOB stammen,
oder die binären Daten aus dem BLOB vorher entschlüsselt oder dekomprimiert werden müssen. Dazu steht eine andere Variante der Redefined
-Methode zur Verfügung:
public TTgt Redefined<TTgt, TDefTgt>(Byte[] data, DataStructure<TTgt, TDefTgt> tgtStructure) where TTgt : DataObject<TTgt> where TDefTgt : DataStructure<TTgt, TDefTgt>
Auch hier sieht die generische Methodendefinition relativ komplex aus. In der Praxis wird das wie in folgendem Beispiel benutzt:
var data = Decode(objCtx.Ar1VorBlob); var structured = this.Data.Redefined(data, CtxdbVorBlobVar001.Definition); structured.SessionID = GetSessionID(); obj.Ar1VorBlob = Encode(structured.ToByte());
Man beachte, dass in diesem Fall die Daten in dem erzeugten Datenobjekt nicht direkt mit dem Daten des Objektes verbunden sind.
Falls die Daten in die Ausgangsdaten zurückgeführt werden müssen, kann bspw. die ToByte
-Methode auf dem Datenobjekt benutzt werden.
Bei dieser Nutzung sollte die verwendete Struktur nicht wiederum ein BLOB-Feld enthalten!
Ansonsten gelten hier dieselben Warnungen bzgl. Ausnahmen und Anzahl lokaler Objekte wie oben.
Mit der Methode „Trace“8) kann man beim Aufzeichnen der Bausteinausführung den aktuellen Inhalt eines Datenobjektes aufzeichnen. Die mit der Trace-Methode aufgezeichneten Dateninhalte werden in TestEdge als „Objekt-Snapshots“ bezeichnet.
Als Parameter benötigt die Trace-Methode einen Namen für den Snapshot. Für jeden Aufruf der Methode sollte man nach Möglichkeit einen neuen, eindeutigen Namen für den Snapshot vergeben. Ist das nicht der Fall, werden die wiederkehrenden Snapshot-Namen spätestens beim Lesen der Aufzeichnung durch Hinzufügen einer Nummer eindeutig gemacht.