MesoXPO Framework
Benutzerdefinierte Tabellen und Objekte
Inhalt
- 1 Inhalt
- 2 ⚠️ Workaround für Belegtabelle T026
- 3 Datalayer XPDictionary (Metadaten)
- 3.1 Filterung einer XPCollection mit benutzerdefinierten Spalten
- 3.1.1 Weiterführende Links
- 3.2 DataLayerManager Hilfsmethoden
- 3.3 Arbeiten mit abgeleiteten Klassen
- 3.3.1 Weiterführende Links
- 3.3.2 Vorteile und Herangehensweise
- 3.3.2.1 Beispiel Ableitungen
- 3.3.2.1.1 Klassendefinitionen
- 3.3.2.1.2 Exemplarische Verwendung
- 3.3.2.2 Verwendete Methoden
- 3.3.2.1 Beispiel Ableitungen
- 3.1 Filterung einer XPCollection mit benutzerdefinierten Spalten
- 4 CompanyBase - Methoden
⚠️ Workaround für Belegtabelle T026
Wenn von T026 BestelldateiMitte abgeleitete Klassen verwendet werden, dann führt die dafür notwendige Spalte ObjectType in der aktuellen WinLine Version Edition 2025 dazu, dass die Inhalte der benutzerdefinierten Spalten nicht gespeichert werden
Sub ErmitteleSpaltenWerte()
Dim sql, UserNumber, PaletteOriginal, Spaltennummer, VarNummer
UserNumber = right( "000" & cwlstart.CurrentUser.Number, 3)
Set myvars = CWLCompany.ModifiedVars
sql = "Select * FROM G02W245" & UserNumber & " (NOLOCK) WHERE c062='" & myvars.Value(26,0) & "'"
PaletteOriginal = myvars.value(26,506)
Palette = 0
Set res = cwlstart.CurrentCompany.Connection.Select(sql)
If res>=0 Then
' Benutzerdefinierte Spaltenwerte aus temporärer Grid-Tabelle laden
' Palette
Spaltennummer = 156
VarNummer = 506
myvars.value(26,VarNummer) = res.Value("c" & Cstr(Spaltennummer))
' Rabatttext 1
Spaltennummer = 153
VarNummer = 504
myvars.value(26,VarNummer) = res.Value("c" & Cstr(Spaltennummer))
' Rabatttext 2
Spaltennummer = 154
VarNummer = 505
myvars.value(26,VarNummer) = res.Value("c" & Cstr(Spaltennummer))
' Frachtkosten
Spaltennummer = 155
VarNummer = 503
myvars.value(26,VarNummer) = res.Value("c" & Cstr(Spaltennummer))
End If
res.close
Set res = Nothing
End Sub
'Ende von (Deklarationen)
Sub CWLCompany_OnInsertTable(TableNo)
If TableNo = 26 Then
Call ErmitteleSpaltenWerte
End If
End Sub
Sub CWLCompany_OnUpdateTable(TableNo)
If TableNo = 26 Then
Call ErmitteleSpaltenWerte
End If
End Sub
Datalayer XPDictionary (Metadaten)
Die DataLayer, welche vom DataLayerManager (siehe Herstellen einer Datenverbindung) erzeugt werden, enthalten ein um die benutzerdefinierten Tabellen und Spalten erweitertes XPDictionary (https://docs.devexpress.com/XPO/DevExpress.Xpo.Metadata.XPDictionary), sofern bei der Methode CreateDataLayer die Parameter loadUserdefinedTables (für neue Tabellen) und/oder loadUserdefinedColumns für neue Spalten in Standard-Tabellen gesetzt werden.
Zwar können naturgemäß keine Linq-Abfragen für dynamisch erweiterte Objekte erzeugt werden, jedoch können z.B. XpCollection (https://docs.devexpress.com/XPO/DevExpress.Xpo.XPCollection ) Klassen verwendet werden, die dann z.B. an ein GridControl (https://docs.devexpress.com/WindowsForms/DevExpress.XtraGrid.GridControl) gebunden werden können.
Dieses erzeugt dann automatisch eine Column je PersistentProperty - auch derjenigen, die zur Laufzeit erzeugt wurden.
Hier ein Beispiel für die Erzeugung einer XpCollection für die BestelldateiMitte (t026) welche um benutzerdefinierte Spalten erweitert wurde:
using var uow = MesoObjectLayer.GetDataUnitOfWork();
var classInfoT026 = uow.GetClassInfo(typeof(BestelldateiMitte));
var xpCollectionT026 = new XPCollection(uow, classInfoT026);
gridControl1.DataSource = xpCollecionT026; Im DataGrid stehen alle persistenten Properties - inkl. der benutzerdefinierten - zur Verfügung:
Filterung einer XPCollection mit benutzerdefinierten Spalten
Hier ein Beispiel für die Erzeugung einer XPCollection für den BestelldateiKopf (t025) welche um benutzerdefinierte Spalten erweitert wurde:
var connectionStringSystem = MSSqlConnectionProvider.GetConnectionString(
"DEMOSRV01\\DEMO", "meso", "*********", "CWLSYS"); ;
using var uowSystem = new SystemUnitOfWork(connectionStringSystem);
// Benutzerdefinierte Spalten und Tabellen in das XPDictionary laden
using var dal = DataLayerManager.CreateDataLayer(uowSystem, "500M",loadUserdefinedColumns:true, loadUserdefinedTables:true);
// ObjectLayer für automatische Filterung auf aktuelles Wirtschaftsjahr
var mesoObjectLayer = new MesoObjectLayer(dal, "500M");
using var uow = mesoObjectLayer.GetDataUnitOfWork();
var classInfoT025 = uow.GetClassInfo(typeof(BestelldateiKopf));
var xpCollectionT025 = new XPCollection(uow, classInfoT025, CriteriaOperator.Parse("StartsWith([Kennzeichen],'TS')"));
xpCollectionT025.Load();
foreach (var beleg in xpCollectionT025.OfType<BestelldateiKopf>())
{
Debug.WriteLine($"Beleg {beleg.Mesoprim} hat Kennzeichen {beleg.GetColumnValueDirect<string>("U000")}");
}Weiterführende Links
Criteria Language Syntax | Cross-Platform Class Library | DevExpress Documentation
How to: Build Complex Criteria | eXpress Persistent Objects | DevExpress Documentation
DataLayerManager Hilfsmethoden
LoadUserDefiniedTablesAndColumns
Dadurch werden die in der angegebenen Datenbankverbindungszeichenfolge gespeicherten benutzerdefinierten Tabellen und Spalten aus der Tabelle T132 geladen und in das XPDictionary angefügt.
/// <summary>
/// Lädt benutzerdefinierte Tabellen und Spalten in das vorhandene XPDictionary
/// </summary>
/// <param name="connectionString"></param>
/// <param name="dict"></param>
public static void LoadUserdefinedTablesAndColumns(string connectionString, XPDictionary dict)Arbeiten mit abgeleiteten Klassen
Im Test in Version 1.2.4-beta-1 https://css-edv-support.atlassian.net/wiki/spaces/MXP/pages/54231096/Releasenotes#1.2.4-beta-1-(-Beta-%F0%9F%94%A7--)
Dies ist nur möglich indem die betroffenen Mesonic WinLine Tabellen um die Spalte ObjectType erweitert wird.
In der Mesonic Datenbank wird zusätzlich die Tabelle XpObjectType angelegt, in welcher die Klassen sowie derer abgeleiteten Klassen mit eindeutigen Id’s gespeichert werden. Diese Id ermöglicht dem Xpo-Framework den Objekttyp auf Datensatzebene zu erkennen.
Der Zugriff auf die Daten ist dann nur noch mit der abgeleiteten Klasse möglich, da der ObjectType für alle Datensätze auf den neuen Typ gesetzt wird (sofern die Methoden des MesoXPO-Frameworks verwendet werden)
Bevor im WinLine Admin weitere benutzerdefinierte Spalten oder Tabellen erzeugt werden, muss die ObjectType Spalte wieder entfernt werden.
Dies kann entweder manuell per SQL oder über die Methode DataLayerManager.DropObjectTypesForUserDefinedTypes erfolgen
Nicht gemeinsam mit Parameter loadUserdefinedTables vom DatalayerManager verwenden, da es sonst zu Konflikten bei den persistenten Klassen im Dictionary kommt, wenn diese auch über abgeleitete Klassen definiert werden.
Sollte man die abgeleiteten Klassen wieder entfernen möchten, müssen die Datensätze aus der Tabelle XPObjectType entfernt werden (entweder per SQL oder im Code, z.B. mituow.ExecuteNonQuery($"DELETE FROM XPObjectType WHERE AssemblyName='{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}'");)
Weiterführende Links
Vorteile und Herangehensweise
Wenn benutzerdefinierte Spalten der Mesonic Standard-Tabellen über Vererbung implementiert werden, so hat man den Vorteil, dass die Klasse inkl. benutzerdefinierter Felder im Code vollständig zur Verfügung steht, z.B. für Linq.
Damit das Xpo-Framework jedoch erkennen kann um was für ein Objekt sich ein Datensatz handelt (also um eine Instanz der Basisklasse oder eine Instanz einer abgeleiteten Klasse) ist die Spezifikation mittels ObjectType Spalte erforderlich. Diese Spalte ist wiederum eine Referenz auf die Tabelle XpObjectType (siehe XPObjectType Class | eXpress Persistent Objects | DevExpress Documentation), welche die definierten persistenten Klassen speichert.
Um das Anlegen der notwendigen Tabelle und Spalte in der Mesonic Datenbank kann die Methode CreateObjectTypesForUserDefinedTypes der Klasse DataLayerManager verwendet werden (siehe Verwendete Methoden).
Darüber hinaus müssen die Typen bei der CreateDataLayer Methode im Parameter userdefinedTypes übergeben werden.
Beispiel Ableitungen
Klassendefinitionen
/// <summary>
/// Von ArtikelPreise T023 abgeleitet
/// </summary>
[MapInheritance(MapInheritanceType.ParentTable)]
public class ArtikelPreiseMitIstos : ArtikelPreise
{
public ArtikelPreiseMitIstos(Session session) : base(session)
{
}
private double istosMaterialkosten = default;
/// <summary>
/// U000 - Istos Materialkosten (kalkulatorisch)
/// (Länge 8, SQL Datentyp System.Data.SqlTypes.SqlDouble)
/// </summary>
[Persistent("U000")]
[Display(Name = "Istos Materialkosten (kalkulatorisch)")]
public double IstosMaterialkosten { get => istosMaterialkosten; set => SetPropertyValue(nameof(IstosMaterialkosten), ref istosMaterialkosten, value); }
}
/// <summary>
/// Von BestelldateiKopf T025 abgeleitet
/// </summary>
[MapInheritance(MapInheritanceType.ParentTable)]
public class BestelldateiKopfKennzeichen : BestelldateiKopf
{
public BestelldateiKopfKennzeichen(Session session) : base(session)
{
}
private string kfzkennzeichen = default;
/// <summary>
/// U000 - KfZ Kennzeichen
/// (Länge 20, SQL Datentyp System.Data.SqlTypes.SqlNvarchar)
/// </summary>
[Persistent("U000")]
[Display(Name = "KfZ Kennzeichen")]
[Size(20)]
public string KfzKennzeichen { get => kfzkennzeichen; set => SetPropertyValue(nameof(KfzKennzeichen), ref kfzkennzeichen, value); }
private string anhaenger = default;
/// <summary>
/// U001 - Anhänger
/// (Länge 20, SQL Datentyp System.Data.SqlTypes.SqlNvarchar)
/// </summary>
[Persistent("U001")]
[Display(Name = "Anhänger")]
[Size(20)]
public string Anhaenger { get => anhaenger; set => SetPropertyValue(nameof(Anhaenger), ref anhaenger, value); }
}
/// <summary>
/// Neue benutzerdefinierte Tabelle T699 in der WinLine-Datenbank
/// </summary>
[Persistent("T699")]
[IsUserdefinedTable]
public class Testtabelle : MesoBase
{
public Testtabelle(Session session) : base(session)
{
}
private string id = default;
/// <summary>
/// U000 - Id
/// (Länge 20, SQL Datentyp System.Data.SqlTypes.SqlNvarchar)
/// </summary>
[Persistent("U000")]
[Display(Name = "Eindeutiger Key")]
[Size(10)]
[DevExpress.Xpo.Key]
public string Id { get => id; set => SetPropertyValue(nameof(Id), ref id, value); }
private string wert = default;
/// <summary>
/// U001 - Wert
/// (Länge 255, SQL Datentyp System.Data.SqlTypes.SqlNvarchar)
/// </summary>
[Persistent("U001")]
[Display(Name = "Wert")]
[Size(255)]
public string Wert { get => wert; set => SetPropertyValue(nameof(Wert), ref wert, value); }
}Exemplarische Verwendung
var connectionStringSystem = MSSqlConnectionProvider.GetConnectionString(
"DEMOSRV01\\DEMO", "meso", "meso@demo", "CWLSYS"); ;
using var uowSystem = new SystemUnitOfWork(connectionStringSystem);
// Abgeleitete persistente Klassen
var newTypes = new Type[]
{
typeof(ArtikelPreiseMitIstos),
typeof(BestelldateiKopfKennzeichen),
typeof(Testtabelle)
};
// Anlegen der ObjectTypes
var dict = DataLayerManager.CreateObjectTypesForUserDefinedTypes(connectionStringSystem, newTypes, false);
using var dal = DataLayerManager.CreateDataLayer(uowSystem, "500M", additionalTypes: newTypes);
// ObjectLayer für automatische Filterung auf aktuelles Wirtschaftsjahr
var mesoObjectLayer = new MesoObjectLayer(dal, "500M");
using var uow = mesoObjectLayer.GetDataUnitOfWork();
foreach (var artikelPreis in new XPQuery<ArtikelPreiseMitIstos>(uow).Take(10))
Debug.Print($"{artikelPreis.Preisartikelnummer}:{artikelPreis.IstosMaterialkosten}");
foreach (var beleg in new XPQuery<BestelldateiKopfKennzeichen>(uow).Take(10))
Debug.Print($"{beleg.Mesoprim}:{beleg.KfzKennzeichen}");
// Eigene Tabelle T699
var testobject = uow.GetObjectByKey<Testtabelle>("TestId1");
if (testobject != null)
{
uow.Delete(testobject);
uow.CommitChanges();
}
testobject = new Testtabelle(uow)
{
Id = "TestId1",
Wert ="Mein Testwert"
};
uow.CommitChanges();
Verwendete Methoden
Der DataLayerManager verwendet hierbei die Methoden DropObjectTypesForUserDefinedTypes und CreateObjectTypesForUserDefinedTypes; insbesondere die Methode DropObjectTypesForUserDefinedTypes ist für Projekte relevant, da die Spalte vor einem Update oder Datenbankupsize oder Erweiterung der Tabellen um eigene Spalten entfernt werden muss.
/// <summary>
/// Löscht die Spalte ObjectType aus den Basisklassen der angegebenen benutzerdefinierten Typen.
/// Ist der Typ nicht abgeleitet, wird auch nichts durchgeführt
/// </summary>
/// <param name="connectionStringMesoSystem"></param>
/// <param name="userdefinedTypes"></param>
public static void DropObjectTypesForUserDefinedTypes(string connectionStringMesoSystem, Type[] userdefinedTypes)
/// <summary>
/// Erzeugt die XPObjectType Datensätze in der Tabelle XPObjectType (und legt diese ggf. auch an)
/// Erweitert bei abgeleiteten Klassen die Basistabellen um die Spalte ObjectType und setzt den ObjecType auf
/// die vergebene ID in der Tabelle XPObjectType
/// </summary>
/// <remarks>
/// ACHTUNG: Hierdurch wird die Mesonic Tabellenstruktur um eine Spalte erweitert, die NICHT im Repository bekannt ist
/// Das bedeutet, wenn Datenbankupsizes gemacht werden geht diese Spalte ggf. verloren und muss neu erzeugt werden
///
/// Um weitere benutzerdefinierte Spalten zu erzeugen MUSS die Spalte vorher entfernt werden.
/// Hierfür den <paramref name="dropColumn"/> auf 'true' setzen
/// </remarks>
/// <param name="connectionStringMesoSystem"></param>
/// <param name="userdefinedTypes"></param>
/// <param name="dropColumn"></param>
/// <returns>Das XPDictionary Objekt welches die benutzerdefinierten Typen enthält</returns>
public static XPDictionary CreateObjectTypesForUserDefinedTypes(string connectionStringMesoSystem, Type[] userdefinedTypes, bool dropColumn = false)CompanyBase - Methoden
Alle Mandantenobjekte leiten von der Klasse CompanyBase ab. Diese stellt folgende Methoden zum Zugreifen auf benutzerdefinierte Spalten bereit:
GetUserdefinedColumnValue
Ermittelt den Wert der in "columnNumber" angegebenen Spalte als Typ T
public T GetUserdefinedColumnValue<T>(int columnNumber)
Beispiel zum Ermitteln der benutzerdefinierten Spalte U000 für einen Artikel als int:
Assert.NotNull(artikel.GetUserdefinedColumnValue<int>(0), "Product 50001 has no user definied Column 0 of int");SetUserdefinedColumnValue
Setzt den Wert der in "columnNumber" angegebenen Spalte als Typ T
public void SetUserdefinedColumnValue<T>(int columnNumber, T value)
Beispiel zum Setzen der benutzerdefinierten Spalte U000 für einen Artikel auf den Wert “1” als int:
artikel.SetUserdefinedColumnValue<int>(0, 1);