Datenobjekte

In der Basis-Assembly werden automatisch für alle potentiell in Frage kommenden Objekte die dazu passenden Datenstrukturen generiert. Dabei wird die Notwendigkeit aus den Schnittstellenbeschreibungen der in der Assembly generierten Bausteine abgeleitet. Nicht nur die Parameterobjekte und globalen Objekte werden dabei ausgewertet, sondern auch die Objekte, die an Bausteine aus der jeweiligen Aufrufstruktur1) übergeben werden können. Sollten im Ausnahmefall darüberhinausgehende Datenstrukturen benötigt werden, so können diese explizit bei der ExpEdge-Generierung angegeben werden.

Allgemein

Boolean IsReadonly { get; }
Boolean IsInInitializedState { get; }
String SchwebeKennzeichen { get; }
DateTime? SchwebeKennzeichenTs { get; }
Boolean Dump(String filePath);
void Reset();
Module Module { get; }
String ObjectName { get; }
Boolean Trace(String name);

Alle Datenobjekte, egal ob Einzelobjekt oder Mengenobjekt, implementieren das Interface IDataObject. Über dieses Interface besitzt jedes Datenobjekt eine Eigenschaft IsReadonly, mit der geprüft werden kann, ob das Objekt verändert werden darf. Dies wird u.a. durch seine Rolle zur Laufzeit bestimmt.

Ebenfalls besitzt jedes Datenobjekt eine Eigenschaft IsInInitializedState, die angibt, ob der Inhalt des Objektes dem Zustand der Initialisierung entspricht. Bei Mengenobjekte ist diese Eigenschaft true, wenn das Mengenobjekt entweder gar keine Einträge hat, oder alle Sätze im Mengenobjekt dem Initialzustand entsprechen. Bei einem Einzelobjekt, oder bei der Überprüfung eines Satzes aus einem Mengenobjekt, ist diese Eigenschaft dann true, wenn Daten für den Satz oder das Objekt noch gar nicht allokiert wurden, oder der Inhalt dieser Daten dem Initialwert entspricht. Wenn der Dateninhalt durch schreibende Operationen verändert wurden aber dennoch dem Initialwert entsprechen, ist diese Eigenschaft true. Es wird also auf Inhalt geprüft, nicht auf Änderungen.

Jedes Datenobjekt kennt auch eine Methode Reset, mit der der Inhalt des Objektes auf Ausgangslage2) zurückgesetzt werden kann

Außerdem kennt jedes Datenobjekt die Methode Dump(<filename>). Diese Methode schreibt den kompletten Inhalt des Datenobjekts in eine Datei. Der Pfadname der Datei wird der Methode als Argument übergeben. Dieses Feature ist nur für Testzwecke gedacht. Beim Einsatz unter Endbenutzer-Bedingungen werden die Dump-Anweisungen ignoriert. Außerdem kann man über die Registry das Verhalten zusätzlich gezielt beeinflussen.

Die Eigenschaft Module3) liefert eine Referenz zurück zu dem Baustein, der die Referenz auf das aktuelle Datenobjekt verwaltet.

Desweiteren besitzt jedes Datenobjekt die Eigenschaften SchwebeKennzeichen. Wenn diese Eigenschaft gesetzt (!String.IsNullOrEmpty) ist, ist der Inhalt des Objektes in Verbindung zu einer bestehenden Schwebeklammer mit dem angegebenen Kennzeichen zu sehen.

if (!Data.Lptgesl.IsReadOnly && !String.IsNullOrEmpty(Data.Lptgesl.SchwebeKennzeichen)) {
	Data.Lptgesl.Dump(Path.Combine(strDumpPath, "lptgesl.dmp"));
}

Zusätzlich steht die Eigenschaft SchwebeKennzeichenTs zur Verfügung. In der Annahme, dass das Schwebekennzeichen in Wirklichkeit ein Timestamp ist, wird diese konvertiert in ein DateTime angeboten. Falls kein Schwebekennzeichen gesetzt ist, oder das Schwebekennzeichen nicht als Timestamp formatiert ist, oder nicht konvertiert werden kann, ist die Eigenschaft SchwebeKennzeichenTs null.

if (Data.Lptgesl.SchwebeKennzeichenTs.HasValue)) {

Die Eigenschaft ObjectName4) liefert den Namen, unter dem es in Module bekannt ist. Für Parameter- und globale Objekte ist das der Name, so wie er in der Schnittstelle definiert ist. Für lokale Objekte ist es der Name, der bei New bzw. NewList angegeben bzw. erzeugt wurde.

Auch die Methode Trace5) für das Aufzeichnen eines Objekt-Snapshots kann über IDataObject ausgelöst werden.

Interfaces

Wenn bei der Generierung der Basisassembly auch Schnittstellendefinitionen6) angegeben wurden, wird nicht nur die Definition dieser Schnittstelle generiert, sondern werden auch alle Datenobjekte, die zur Schnittstellenstruktur passen, mit dieser Schnittstelle generiert. Wenn bspw. nachfolgende Schnittstelle erzeugt wurde:

public interface IVertragsteil: IData<IVertragsteil>
{
	/// <summary>LVT-LVT-LNR: Nummer zur eindeutigen identifizierung eines Vertragsteils</summary>
	UInt16 LvtLvtLnr { get; set; }
	/// <summary>VSG-VSG-LNR: Versicherungs-Vertrag-Laufende-Nummer</summary>
	UInt64 VsgVsgLnr { get; set; }
}

Wird die Definition der entsprechenden Datenobjekte um eine solche Schnittstelle ergänzt:

public sealed class Aalvt0: DataObject<Aalvt0>, IVertragsteil { }

Es können durchaus mehrere Schnittstellen zu einem Datenobjekt passen, diese werden alle in der Definition der Klasse berücksichtigt.

Einzelobjekte oder Datensätze

Die Grundlage für alle Datenobjekte ist die Struktur für ein Einzelobjekt. Ein Mengenobjekt ist „nur“ eine Liste von Einzelobjekten.

Datenelemente vs. Datengruppen

Alle Datenelemente des Objektes können direkt auf erster Ebene angesprochen werden, auch wenn diese unter einer Gruppenstufe definiert wurden. Ebenso können auch die Gruppenstufen direkt angesprochen werden. Wenn eine Gruppenstufe einen als Zeichenfolge darstellbaren Inhalt besitzt, kann diese auch insgesamt abgefragt, nicht jedoch verändert werden. Die Darstellung der Gruppenstufe passiert explizit oder implizit über die ToString Methode.

// implizite Nutzung von ToString um den Inhalt der Gruppenstufe darzustellen
var strObUndRolle = String.Format("{0}", recBlptn.ObUndRolle);
var strObUndRolle = (String) recBlptn.ObUndRolle;
 
// explizite Nutzung von ToString um den Inhalt der Gruppenstufe darzustellen
var strObUndRolle = recBlptn.ObUndRolle.ToString();
 
// ohne ToString: Nutzung der Gruppenstufe als Datenbereich mit Einzelfelder
var dtaObUndRolle = recBlptn.ObUndRolle;
var fldId = dtaObUndRolle.SId;

Nachfolgend nochmals der gleiche Code, nun jedoch mit expliziten Typangaben.

// implizite Nutzung von ToString um den Inhalt der Gruppenstufe darzustellen
String strObUndRolle = String.Format("{0}", recBlptn.ObUndRolle);
String strObUndRolle = (String) recBlptn.ObUndRolle;
 
// explizite Nutzung von ToString um den Inhalt der Gruppenstufe darzustellen
String strObUndRolle = recBlptn.ObUndRolle.ToString();
 
// ohne ToString: Nutzung der Gruppenstufe als Datenbereich mit Einzelfelder
Blptlt.ObUndRolle_Data dtaObUndRolle = recBlptn.ObUndRolle;
UInt64 fldId = dtaObUndRolle.SId;

Die Datenelemente einer Gruppenstufe können auch unterhalb der Gruppe referenziert werden. So wird in den nachfolgenden Codezeilen immer das gleiche Feld angesprochen:

var fldId = recBlptn.SId;
var fldId = recBlptn.ObUndRolle.SId;
var fldId = recBlptn.ObUndRolle.SachId.SId;

Wenn Datenelemente mit gleichem Namen in mehreren Gruppenstufen vorkommen, sind solche Felder, um Namenskonflikte zu vermeiden, teilweise in den übergeordneten Gruppen nicht verfügbar.

Kopieren

void CopyTo(T rec);

Um einen vollständigen Datensatz oder eine Datengruppe zu kopieren, steht die Methode CopyTo zur Verfügung. Hierbei werden sämtliche Felder des Ursprungssatzes/der Ursprungsgruppe auf einem Schlag in die Felder des Zielsatzes/der Zielgruppe überführt. Wenn Quelle und Ziel identisch sind, ist diese Operation eine No-Op. Beispielcode:

// Datensatz:
foreach (var rec in src) {
	rec.CopyTo(dst.Add());
}
// Datengruppe:
rec.grp1.CopyTo(rec2.grp1);

Oder mit ordentlichem Dispose:

foreach (var rec in src) using(rec) {
	using (var dstRec = dst.Add()) {
		rec.CopyTo(dstRec);
	}
}

Dieser Code ist nur zur Veranschaulichung der CopyTo-Methode für Datensätze gedacht. In Wirklichkeit würde man obige Funktionalität eher wie folgt realisieren:

foreach (var rec in src) {
	dst.Add(rec);
}

Vergleichen von Datenobjekten, Datensätzen und Gruppeninhalten

Eine Datenelementgruppe implementiert das IComparable<T> Interface. Über diesen Weg können Gruppeninhalte auch dann, wenn diese nicht als String abgebildet werden, miteinander verglichen werden:

// Vergleich immer falsch, da die Instanzen verglichen werden
bEqual = pvgob[0].PvDatenGob != pvgob[1].PvDatenGob;
 
// Inkorrekter Vergleich, da im Falle von Binärdaten auch bei unterschiedlichen
// Inhalten die gleiche Zeichenfolge ([binary data]) geliefert wird.
bEqual = pvgob[0].PvDatenGob.ToString() != pvgob[1].PvDatenGob.ToString();
 
// korrekter Vergleich der Inhalte
bEqual = pvgob[0].PvDatenGob.CompareTo(pvgob[1].PvDatenGob) != 0;

Auch für Einzelobjekte und Datensätze (DataObject<T>) sowie für Mengenobjekte (ObjList<DataObject<T>>) ist das IComparable<T> Interface implementiert. Durch die generische Lösung wird auch hier sichergestellt, dass typunverträgliche Vergleiche bereits beim Editieren und Kompilieren festgestellt werden.

Die Methode CompareTo auf Datensätze oder Objekte vergleicht das aktuelle Objekt mit dem übergebenen Objekt. Das Ergebnis ist 0, wenn beide Objekte gleich sind, -1, wenn der Inhalt des aktuellen Objekts kleiner ist als der des übergebenen Objekts, 1, wenn der Inhalt des aktuellen Objekts größer ist. Wenn eines der beiden Objekte ein Einzelobjekt ist, wird der Inhalt des Datensatzes mit dem Inhalt des aktuellen Satzes des Mengenobjekts verglichen. Beim Vergleich zweier Mengenobjekte werden deren Sätze Stück für Stück miteinander verglichen, bis ggf. der erste abweichende Satz gefunden wird.

Mengenobjekte sind immer unterschiedlich, wenn die Anzahl Sätze ungleich sind. Datensätze sind immer unterschiedlich, wenn mindestens einer der beiden Sätze ein Datenelement vom Typ BLOB oder CLOB enthält, welches nicht null ist.

Verbindung zum TAA Objektmanager

Es ist wichtig zu verstehen, dass jede Referenz auf die Bestandteile des Datenobjektes immer auf die definierte Datenstruktur und den TAA Objektmanager zurückgeführt wird. Änderungen über verschiedene Referenzen wirken sich aufeinander aus, da die zugrundeliegende Daten gemeinsam benutzt werden:

// besorge Referenzen
var obj = this.Data.Lptgesl;
var rec = obj.Add();
var pos = obj.IndexOf(rec);
var grp = rec.ObUndRolle;
 
// folgende Zuweisungen sind identisch
grp.SId = 23;
rec.SId = 23; 
obj[pos].SId = 23;
this.Data.Lptgesl[pos].ObUndRolle.SachId.SId = 23;

Ebenso ist es wichtig zu verstehen, dass die wirklichen Datentypen eingeschränkter sein können als die CLR Datentypen. In der Definition der Datenstrukturen können detailliertere Angaben vorgenommen werden, die eine Einschränkung gegenüber der möglichen Speicherung in dem jeweiligen CLR Datentyp darstellen können:

// in Wirklichkeit verschwindet die Uhrzeit, weil DATE
pvgob[0].GobGbZstGabD0 = DateTime.Now;
Console.WriteLine(pvgob[0].GobGbZstGabD0); // 12.05.2015 00:00:00
 
// wird abgeschnitten, weil als zweistellig definiert
pvgob[0].GobGbZstK = UInt16.MaxValue;
Console.WriteLine(pvgob[0].GobGbZstK); // 35
 
// wird abgeschnitten weil einstellig definiert
pvgob[0].GobVnWechKz = "XXX";
Console.WriteLine(pvgob[0].GobVnWechKz); // X
Wenn Daten abgeschnitten werden, wird keine Ausnahme wie OverflowException oder ähnlich aufgeworfen.

Datenelementreihen (OCCURS)

Wenn ein Datenelement eine OCCURS-Angabe beinhaltet, wird das Element je nach Anzahl Dimensionen an der entsprechenden Stelle als DataFieldArray<T>, DataFieldVector<T> oder DataFieldMatrix<T> zur Verfügung gestellt. Das Element kann mit der Anzahl dementsprechenden Indices referenziert und auch verändert werden. Außerdem implementieren die drei o.g. Klassen das IEnumerable<T> Interface, mit der die Werte der Datenelemente aufgelistet werden können. Nachfolgend einige Codebeispiele.

Da dies in einer .NET-Anwendung üblicher ist, sind alle Listen 0-basiert. Das ist eine Abweichung von den TAA-Zugriffsmechanismen in anderen Implementierungsumgebungen.
// lese einen Wert
var val = tLevOpAvis.IeLevStzaTab.LevStzaElem[k];
 
// setze den Wert eines Elementes
tLevOpAvis.IeLevStzaTab.LevStzaElem[j] = "X";
 
// setze einen Wert für alle Elemente
for (var i = tLevOpAvis.IeLevStzaTab.LevStzaElem.GetLowerBound();
   i <= tLevOpAvis.IeLevStzaTab.LevStzaElem.GetUpperBound();
   i++)
{
   tLevOpAvis.IeLevStzaTab.LevStzaElem[i] = new String(new [] {(Char) ('A' + i)});
}
 
// verarbeite alle Elemente
foreach (var entry in tLevOpAvis.IeLevStzaTab.LevStzaElem) {
   // do something
}
 
// benutze LINQ um aus den Elementen was abzuleiten
var bDoCalc = tLevOpAvis.IeLevStzaTab.LevStzaElem.Any(e => String.CompareOrdinal(e, "K") == 0);
 
// benutze LINQ um bestimmte Elemente zu verarbeiten
foreach (var entry in tLevOpAvis.IeLevStzaTab.LevStzaElem.Where(e => e.StartsWith("-"))) {
   // do something
}

Es werden neben dem Wert (über den sog. indexer) folgende Eigenschaften und Methoden analog zu System.Array unterstützt:

  • GetLowerBound(<Dimension>): liefert den tiefstmöglichen Wert für ein Element der angegebenen Dimension. Derzeit immer 0.
  • GetUpperBound(<Dimension>): liefert den höchstmöglichen Wert für ein Element der angegebenen Dimension.
  • GetLength(<Dimension>): liefert die Anzahl Elemente in der angegebenen Dimension.
  • Length: Gesamtzahl der Elemente in allen Dimensionen.
  • LongLength: Gesamtzahl der Elemente in allen Dimensionen als 64-Bit Ganzzahl.
  • Rank: Gesamtanzahl an Dimensionen.
  • IsFixedSize: gibt an, ob die Reihe eine feste Länge hat. Derzeit immer wahr.
  • IsReadOnly: gibt an, ob die Reihe schreibgeschützt ist. Entspricht der Eigenschaft IsReadOnly des zugrunde liegenden Datenobjektes.
  • IsSynchronized: gibt an, ob der Zugriff auf das Array synchronisiert (threadsafe) ist. Derzeit immer unwahr.
  • SyncRoot: rut ein Objekt ab, mit der der Zugriff synchronisiert werden kann.

Datentypen

Nachfolgend finden Sie eine Aufstellung der in der Datenmodellierung definierten Datentypen und die daraus abgeleiteten Datentypen für die generierten Datenelemente. Dabei findet die Entscheidung für den jeweiligen Datentyp ebenfalls in der unten dargestellten Reihenfolge statt.

Typ aus dem Datenmodell Art CLR Datentyp7)
C CHARACTER String
X ALPHANUMERISCH String
A ALPHABETISCH String
N NUMERISCH BIN2 Int16/Uint16
BIN4 Int32/Uint32
BIN/BIN8 Int64/Uint64
DECIMAL Decimal
FLOAT1 Single
FLOAT2 Double
Sonstige 8):
- mit Nachkommastellen Double
- mehr als 19 Vorkommastellen Decimal
- mehr als 10 Vorkommastellen Int64/Uint64
- mehr als 5 Vorkommastellen Int32/Uint32
- sonst Int16/Uint16
O BINAER Byte[]
L BLOB Byte[]
M CLOB String
D DATE DateTime
E DATETIME DateTime
Z TIMESTAMP DateTime9)
T TIME TimeSpan
G Gruppenstufe wenn alle Datenelemente als Zeichenfolge darstellbar sind String
Bei der Speicherung als Timestamp werden zwar die Nanosekunden übernommen, jedoch ergibt sich dennoch eine Abweichung zu dem Original-Datumswert (Ticks), sodass ein Vergleich des Timestamp-Feldes mit dem Ausgangswert auf Gleichheit i.A. unwahr liefert.
Um den Umstieg von einer Umgebung, in der das niedrigste Datum der 1. Januar des Jahres 1 ist, auf die TAA-Umgebung, in der das niedrigste Datum der 1. Januar 1000 ist, zu erleichtern, werden Felder mit dem Datentyp DATE und DATETIME über die NativeSupport-API derart besorgt, dass diese in der .NET-Umgebung im Falle von 01.01.0001 stattdessen den Wert 01.01.1000 haben. Wenn eine Datumsangabe zum Stichtag 01.01.0001 gespeichert werden soll, wird stattdessen der 01.01.1000 gespeichert.

Domänen

Wenn ein Datenelement auf einer Domäne basiert, der Typ des Datenelementes resp. der Domäne numerisch oder eine Zeichenfolge ist, wird für die Domäne ein eigenständige Klasse mit dem Namen der Domäne generiert, und werden die Datenelemente stattdessen diese Klasse als Datentyp haben. Voraussetzung dazu ist allerdings auch, dass die Domäne mit der Angabe 88-er relevant markiert ist. Die Datenelemente können auf den Ausgangstyp gecastet werden um den tatsächlichen technischen Wert zu erhalten. Ebenso kann ein passender technischer Wert auf die Domänenklasse gecastet werden, um eine Domänenausprägung des Wertes mit seinen zusätzlichen Eigenschaften zu erhalten. In Anlehnung an der COBOL-Syntax kann die entsprechende „Is“-Eigenschaft auch auf true gesetzt werden10). Wird die „Is“-Eigenschaft auf false gesetzt, so ergibt dies - ebenfalls in Anlehnung an COBOL - eine InvalidOperationException.

// IntelliSense unterstützte Zuweisung
this.Data.Hgusteu.VstVrgStArtK = VrgStArtK.VorgangErledigt;
// ist identisch zu
this.Data.Hgusteu.VstVrgStArtK.Value = VrgStArtK.VorgangErledigt;
this.Data.Hgusteu.VstVrgStArtK = "9";
this.Data.Hgusteu.VstVrgStArtK.Value = "9";
this.Data.Hgusteu.VstVrgStArtK.IsVorgangErledigt = true;
 
// oder
var erlVal = "9";
VrgStArtK erlCls = "9";
if (erlVal != erlCls) {
	// should always be true
}

Die generierte Klasse besitzt eine Value-Eigenschaft, mit der der Wert des Elementes abgerufen oder gesetzt werden kann. Außerdem besitzt die Klasse bool-Methoden mit Namen anfangend mit Is für alle definierten Domänenwerte. Ebenfalls ist eine Eigenschaft HasKnownValue verfügbar, die true liefert, falls der aktuelle Wert durch eins der definierten Domänenwerte, Listen oder Bereiche abgedeckt ist, oder false falls der Wert in dieser Domänendefinition unbekannt ist.

Zusätzlich werden für einzelne Werte (nicht für Listen oder Wertebereiche) statische Eigenschaften mit dem Namen des jeweiligen Eintrags in der Domänenklasse erzeugt, die den definierten Wert für den jeweiligen Domäneneintrag liefern.

Zugriff mit Feldname

Für den Fall, dass der Zugriff auf ein Datenelement mit dem Namen als Zeichenfolge erfolgen muss, bietet die Klasse DatatObject<T> die generischen Methoden GetField<T> und SetField<T>, inklusive entsprechender overloads für mehrdimensionale Datenelemente (OCCURS):

public T GetField<T>(String name);
public T GetField<T>(String name, UInt32 idx);
public T GetField<T>(String name, UInt32 idx1, UInt32 idx2);
public T GetField<T>(String name, UInt32 idx1, UInt32 idx2, UInt32 idx3);
 
public void SetField<T>(String name, T value);
public void SetField<T>(String name, T value, UInt32 idx);
public void SetField<T>(String name, T value, UInt32 idx1, UInt32 idx2);
public void SetField<T>(String name, T value, UInt32 idx1, UInt32 idx2, UInt32 idx3);

Durch das Typargument T kann der Datentyp für den Feldinhalt festgelegt werden. Die Indices für mehrdimensionale Datenlemente sind hier ebenfalls 0-basierend.

ToByte

Um den Inhalt des Datenobjektes als Array von Bytes zu erhalten steht die Methode ToByte zur Verfügung:

public Byte[] ToByte();

Mengenobjekte

Ein Mengenobjekt stellt sich zur Laufzeit als eine Liste von Einzelobjekten dar. Ein Mengenobjekt wird durch die generische Klasse ObjList<> abgebildet.

Die Klasse ObjList<T> implementiert das Interface IList<>. Damit sind sämtliche Eigenschaften und Methoden und auch alle sog. Extension Methoden, insbesondere die LINQ Methoden verfügbar.

Wenn über die Klasse ObjList<T> die Daten aufgelistet11) werden, wird vor der Auflistung ein sog. Snapshot des Objektzustandes vorgenommen. Wenn das Mengenobjekt währenddessen verändert wird, werden neu hinzugefügte Datensätze nicht berücksichtigt und führen gelöschte Datensätze zum Auftreten von Conditions.

Die Datensätze können auch über den sog. Indexer durchlaufen werden. Das Mengenobjekt kann also mit einem subscripted array indexer angesprochen werden. Wenn dabei ein Index-Wert außerhalb des verfügbaren Bereiches benutzt wird, führt dies zu einer Ausnahme ArgumentOutOfRangeException.

Methoden und Eigenschaften

IEnumerator<T> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator();
T this[Int32 index] { get; }
 
T Add();
T Add(Action<T> initializer);
void Add(T item);
void Add(ObjList<T> item);
void AddRange(IEnumerable<T> collection);
 
T Insert(Int32 index);
T Insert(Int32 index, Action<T> initializer);
void Insert(Int32 index, T item);
void Insert(Int32 index, ObjList<T> item);
void InsertRange(Int32 index, IEnumerable<T> collection);
 
T Add(DataRow dataRow, T template = null);
IEnumerable<T> Add(IEnumerable<DataRow> dataRows, T template = null);
IEnumerable<T> Add(DataRowCollection dataRows, T template = null);
 
T Insert(Int32 index, DataRow dataRow, T template = null);
IEnumerable<T> Insert(Int32 index, IEnumerable<DataRow> dataRows, T template = null);
IEnumerable<T> Insert(Int32 index, DataRowCollection dataRows, T template = null);
 
void CopyTo(T[] array, Int32 arrayIndex);
 
void Clear();
Boolean Remove(T item);
Boolean Remove(IEnumerable<T> items);
void RemoveAt(Int32 index);
 
Int32 IndexOf(T item);
Int32 Count { get; }
Boolean Contains(T item);
Int32 CompareTo(ObjList<T> other);
 
void Merge(IEnumerable<T> collection, Predicate<T> match);

Die Klasse ObjList<T> implementiert, wie bereits erwähnt, das Interface IList<>. Damit sind sämtliche dort definierten Methoden und Eigenschaften verfügbar, werden aber stellenweise von ObjList<T> noch erweitert. Nachfolgend eine funktionale Aufstellung der verfügbaren Methoden und Eigenschaften.

Datensätze hinzufügen

Für das Hinzufügen von Datensätzen stehen die Methoden Add und Insert zur Verfügung. Der Unterschied besteht darin, dass die Methode Insert eine Kontrolle darüber bietet, an welcher Stelle der Datensatz in der Liste eingefügt werden soll. Die Methode Add überlässt es mehr oder weniger dem Zufall, an welcher Stelle das Objekt in der Liste eingefügt wird; dies kann am Ende, oder auch hinter dem gerade zuletzt referenziertem Element der Liste sein. Bei der Insert-Methode wird als Argument der Index12) übergeben. Dieser legt fest, an welcher Stelle der Datensatz in die Liste eingefügt werden soll. Die in der Liste ggf. nachfolgenden Datensätze rücken dafür dementsprechend nach hinten. Ausnahme: Der Index ist gleich der Anzahl Sätze; in diesem Fall wird der Satz am Ende angefügt. Wenn der angegebene Index kleiner als 0 oder größer als die derzeitige Anzahl Elemente der Liste ist, tritt eine Ausnahme ArgumentOutOfRangeException auf. Ansonsten wird, entsprechend der Definition des Interfaces IList<T>, in allen sonstigen Fehlerfällen eine Ausnahme NotSupportedException aufgeworfen.

Wenn die Methode Add oder Insert ohne Daten aufgerufen wird, wird ein neuer, leerer Datensatz in der Liste eingefügt und dieser Datensatz als Ergebnis der Methode zurückgereicht:

var newRec = l_blptn.Add();
newRec.AenK = "M";
newRec.IekKey.IekInkExkKtoLnr = UInt64.MinValue;

Um eine vergleichbare Funktionalität wie bei einem sog. Object Initializer erreichen zu könenn, kann der Methode Add oder Insert auch ein initialisierender Delegate übergeben werden:

var item = lvvtgt.Add(r => {
   r.VsgVsgLnr = vsgVsgLnr;
   r.LvtLvtLnr = lvtLvtLnr;
});

Die Methode Add oder Insert kann auch mit einem Datensatz als Argument aufgerufen werden. Eine Kopie des Datensatzes wird dabei in der Liste eingefügt:

var recOrg = l_blptn[0];
l_blptn.Insert(1, recOrg);
var newRec = l_blptn[1];
newRec.AenK = "M";
newRec.IekKey.IekInkExkKtoLnr = UInt64.MinValue;

Auch können die Methoden Add und Insert mit einem Mengenobjekt als Argument aufgerufen werden. Der gesamte Inhalt des Mengenobjektes wird dabei in das andere Mengenobjekt kopiert:

var locVorVers = this.Data.NewList(Alvorvers.Type, "locVorVers");
locVorVers.Add(this.Data.Alvovs);
DataRow

Für die Implementierung von Datenzugriffe können die Methoden Add und Insert auch mit einem System.Data.DataRow bzw. eine Liste (IEnumerable<DataRow> oder System.Data.DataRowCollection) von DataRow aufgerufen werden13). Die hinzugefügte Einträge werden als Ergebnis der Methode zurückgereicht, damit sie ggf. weiter verarbeitet werden können. Außerdem kann optional ein template Eintrag übergeben werden, der als Vorlage für die hinzugefügte Einträge dient:

var dt = this.ReadFromOleDb((OleDbConnection) connection, sqlZugriff, Sql.attInDB);
 
if (dt != null && dt.Rows.Count > 0) {
    localItem._000ZgrErg = ZgrErg.Found;
    this.Data.Z0aaq63.Add(dt.Rows, localItem);
}
else {
    localItem._000ZgrErg = ZgrErg.NotFound;
    this.Data.Z0aaq63.Add(localItem);
    this.State.SetFehlerliste();
}

In dem DataRow dürfen nur nicht-dimensionierte Felder verwendet werden (e.g. keine Occurs-Angaben). Auch dürfen keine DataRow-Instanzen verwendet werden, welche den RowState Detached haben.

Datensätze löschen

Zum Löschen von Datensätzen in einem Mengenobjekt stehen folgende Methoden zur Verfügung:

  • Clear(): löscht alle Datensätze in dem Mengenobjekt.
  • Remove(<Datensatz>): löscht den übergebenen Datensatz aus dem Mengenobjekt.
  • Remove(IEnumerable<T> <Datensätze>)14): löscht die übergebenen Datensätze aus dem Mengenobjekt.
  • RemoveAt(<Index>): löscht den Datensatz mit dem angegebenen Index aus dem Mengenobjekt.
  • Siehe auch die List<T> relatierten Methoden RemoveAll und RemoveRange.

Die möglicherweise auftretenden Ausnahmen beschränken sich auf ArgumentException und ArgumentOutOfRangeException für fehlerhafte Argumente und NotSupportedException für logische Fehler. Allerdings können natürlich jederzeit Ausnahmen der Art Condition auftreten.

Wenn mehrere Datensätze gelöscht werden sollen, ist dies am effizientesten mit den Methoden Remove(IEnumerable<T> <Datensätze>), RemoveAll oder RemoveRange auszuführen. Die Implementierung mit vielen vereinzelten Aufrufe der Methode Remove(<Datensatz>) bedeutet einen deutlich größeren Overhead.

Wenn ein Datensatz aus einer Objektliste entfernt wird, ist dieser Datensatz nicht mehr nutzbar. Bis zum Release 9.09 wurden als Besonderheit in der Unterstützung für die Implementierung mittels generierter Basisassemblies gelöschte Datensätze jedoch solange aufbewahrt, wie es noch Referenzen zum Datensatz im Code gibt. Daher war bis zum Release 9.09 in dieser Umgebung - im Gegensatz zu allen anderen Implementierungsumgebungen - folgender Code zulässig:

foreach (var rec in objA) using(rec) {
	objA.Remove(rec);
	objB.Add(rec);
}

Die logisch gelöschten Datensätze wurden dazu im Kontext des Bausteins aufbewahrt, der die Löschung veranlasst hat. Wurde dieser Baustein verlassen, waren die Datensätze endgültig gelöscht, auch wenn es zu dem Zeitpunkt noch .NET-Referenzen auf die Datensätze gibt.

Mit dem Release 9.09 wird dieses Verhalten noch unterstützt, jedoch mit einem Hinweis in Form einer Oops-Meldung quittiert, damit man das Anwendungscoding anpassen kann. Die Meldung sieht wie folgt aus: OopsEntr CheckDetached: Accessing detached Objectdata (<n1>/<n2>/<n3>), wobei zur Vereinfachung der Suche nach der Ursache n1 der ursprüngliche Name des Objektes ist, aus dem der Satz gelöscht wurde, n2 der Namen des Datenobjektes und n3 der Name der Datenstruktur ist.

Konvertieren und Exportieren

Mit der Standard-Methode CopyTo kann der Inhalt des Mengenobjektes in einem Array von Datensätzen überführt werden. Die möglich auftretenden Ausnahmen beschränken sich auf ArgumentException, ArgumentNullException und ArgumentOutOfRangeException. Allerdings können jederzeit Ausnahmen der Art Condition auftreten.

Auskünfte

Zur Prüfung und Auskunft (ohne Datenänderungen) stehen folgende Eigenschaften und Methoden zur Verfügung:

  • IsReadOnly: Mit dieser Eigenschaft kann geprüft werden, ob das Objekt verändert werden darf.
  • Count(): gibt an, wie viele Datensätze derzeit im Mengenobjekt vorhanden sind.
  • IndexOf(<Datensatz>): liefert die Position des Datensatzes im Mengenobjekt.
  • Contains(<Datensatz>): prüft, ob das Mengenobjekt den angegebenen Datensatz beinhaltet.

Bei diesen Eigenschaften und Methoden können höchstens Ausnahmen der Art Condition auftreten.

Merge

Mit der Methode Merge kann der Inhalt des Mengenobjektes aktualisiert werden. Der Inhalt aller Elemente, die die vom angegebenen Prädikat definierte Bedingen erfüllen, werden durch den Inhalt des jeweiligen Elementes aus der Collection, der die Bedingung erfüllt, ersetzt. Hierbei wird die jeweilige Position in der Objektliste beibehalten. Wenn die Collection weniger Elemente enthält als in der Objektliste vorhanden, werden überzählige Elemente gelöscht. Sind mehr Elemente vorhanden, werden diese am Ende angefügt.

List<T> relatierte Methoden

Obwohl die Klasse ObjList<T> das Interface IList<T> implementiert, besitzen Instanzen der Klasse List<T> noch weitere Methoden und Eigenschaften, die für ObjList<T> ebenfalls implementiert sind:

void ForEach(Action<T> action);
List<T> GetRange(int index, int count);
 
bool Exists(Predicate<T> match);
bool TrueForAll(Predicate<T> match);
 
int RemoveAll(Predicate<T> match);
void RemoveRange(int index, int count);
 
T Find(Predicate<T> match);
T FindLast(Predicate<T> match);
List<T> FindAll(Predicate<T> match);
 
int FindIndex(Predicate<T> match);
int FindIndex(int startIndex, Predicate<T> match);
int FindIndex(int startIndex, int count, Predicate<T> match);
 
int FindLastIndex(Predicate<T> match);
int FindLastIndex(int startIndex, Predicate<T> match);
int FindLastIndex(int startIndex, int count, Predicate<T> match);
 
void Sort();
void Sort(IComparer<T> comparer);
void Sort(int index, int count, IComparer<T> comparer);
void Sort(Comparison<T> comparison);

Beispiele und Anmerkungen

Im Großen und Ganzen funktionieren die List<T>-relatierten Methoden sinngemäß so, wie diese für eine List<T>-Instanz beschrieben werden. Zu beachten ist bei all diesen Methoden, dass auch dann, wenn ein Datensatz oder Element aus einer Objektliste in einen anderen Container verschoben wird, der Datensatz oder das Element immer noch eine direkte Verbindung zum TAA Objektmanager hat, und die Daten insofern mit allen Referenzen geteilt werden.

Folgender Code zeigt auch, dass die Elemente sowohl in der Liste als auch im Ausgangsobjekt eine (ggf. abweichende) Position haben.

var sublst = obj.GetRange(2, 3);
sublst.ForEach(rec => this.Services.Monitor.Send($"sublst[{sublst.IndexOf(rec)}] == obj[{obj.IndexOf(rec)}]"));

Liefert als Ausgabe:

sublst[0] == obj[2]
sublst[1] == obj[3]
sublst[2] == obj[4]

Weiter ist zu berücksichtigen, dass die Referenzen auf den Datensatz oder das Element nur für den Dauer des Aufrufs der Action<T>, Predicate<T>, etc. gültig sind.

1)
die nicht notwendigerweise auch als Implementierungsbasisklasse in der Assembly erzeugt wurden
2)
initialisiert
3) , 13) , 14)
ab 9.09
4) , 5)
ab 10.00
6)
Bausteintyp DITF
7)
jeweilige Signed/Unsigned Variante abhängig von der Angabe ob Vorzeichen erlaubt
8)
z.B. gepackt, Integer
9)
mit Sicherstellung der µ-Sekunden
10)
Im Falle einer Range oder List wird der Wert auf dem ersten Wert des Domäneneintrags gesetzt
11)
dafür also einen IEnumerator<T> erstellt wird
12)
0-basiert
dotnet:native:obj · Zuletzt geändert: 26.03.2024 08:48

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