Objektdaten

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.

Globale Objekte und Parameterobjekte

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.

Dateninterfaces

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);

Lokale Objekte

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:

Lokale Cache-Objekte

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) {…}

Redefinitionen von BLOBs

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

  1. die fieldInfo zur Struktur des objSource passt, und
  2. die fieldInfo ein BLOB-Element beschreibt, und
  3. das Rückkehrobjekt passend zum Typ der Datenstruktur structureTarget 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 BLOB
  • Zztaaom1.InvalidObjectHandle - das zu benutzende Ausgangsobjekt ist entweder leer, ungültig oder ist ein Mengenobjekt und hat kein aktuell positioniertes Element
  • Zztaaom1.MismatchStructure - die Daten im BLOB-Element reichen nicht aus, um ein Objekt mit den notwendigen Daten zu bestücken
Aus technischen Gründen bleiben die lokalen Objekte in der Infrastruktur trotz der Freigabe bis zum Unregister des aktuellen Moduls erhalten, sodass bei extensiver Nutzung innerhalb eines Moduls (z.B. in einer Schleife) eine Vielzahl von lokalen Objekten existiert, die erst bei Modulende freigegeben werden. Dies kann Auswirkungen auf Performance und Speicherverbrauch der Anwendung haben.
Die Infrastruktur hat - abgesehen von der Prüfung der Mindestlänge - keine Möglichkeit, zu überprüfen, ob der Inhalt des Blobs zu der angegebenen Datenstruktur passt. Wenn dies nicht der Fall ist, kann es beim Zugriff auf Inhalte des temporären Objekts zu Ausnahmen kommen.

Redefinitionen von BLOBs als Kopie

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.

Objekt-Snapshots aufzeichnen

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.

Die Trace-Methode sollte nur bei Bedarf angewendet werden, da die Aufzeichnungen sonst unnötig groß werden. Die Parameterobjekte (und ggf. auch die globalen Objekte) werden bereits am Anfang und am Ende einer Bausteinausführung aufgezeichnet. Deshalb sollte man die „Trace“-Methode nur für spezifische Testzwecke anwenden, wie z.B. zum Aufzeichnen eines Zwischenstands eines Datenobjektes oder zum Aufzeichnen lokaler Objekte, die sonst beim Aufzeichnen nicht berücksichtigt werden.

Trace Methode

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.

1)
LST - abgebildet durch ObjList<REC>
2)
REC - abgebildet durch eine für die Datenstruktur dediziert generierte Klasse
3)
ab V9.10
4)
Objekttyp, Datenstruktur und Mengenangabe (REC oder LST) werden verglichen
5)
Ab 9.08: die Funktionalität steht nach Neugenerierung der Assembly ab dem Release 9.08 zur Verfügung.
6)
Ab V9.11; mit 9.10 konnte es hier zu Exceptions kommen.
7)
Ab V9.11
8)
ab V9.12
dotnet:native:data · Zuletzt geändert: 09.08.2024 13:25

Copyright © 1992-2024 TeamWiSE Gesellschaft für Softwaretechnik mbH         Adressen |  Kontakt |  AGB |  Datenschutzerklärung |  Impressum