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.
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 Module
3) 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 ObjectName
4) 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 Trace
5) für das Aufzeichnen eines Objekt-Snapshots kann über IDataObject
ausgelöst werden.
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.
Die Grundlage für alle Datenobjekte ist die Struktur für ein Einzelobjekt. Ein Mengenobjekt ist „nur“ eine Liste von Einzelobjekten.
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.
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); }
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.
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
OverflowException
oder ähnlich aufgeworfen.
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.
// 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.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 | DateTime 9) |
|
T | TIME | TimeSpan |
|
G | Gruppenstufe | wenn alle Datenelemente als Zeichenfolge darstellbar sind | String |
unwahr
liefert.
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.
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.
Um den Inhalt des Datenobjektes als Array von Bytes zu erhalten steht die Methode ToByte
zur Verfügung:
public Byte[] ToByte();
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
.
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.
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);
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.
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.
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.
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.
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.
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.
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);
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.
Signed/Unsigned
Variante abhängig von der Angabe ob Vorzeichen erlaubtIEnumerator<T>
erstellt wird