Mithilfe des SysExtension-Frameworks ermitteln, welche Unterklasse in Dynamics AX 2012 instanziiert werden soll
Veröffentlicht: 16. Februar 2025 um 00:25:37 UTC
In diesem Artikel wird beschrieben, wie Sie das wenig bekannte SysExtension-Framework in Dynamics AX 2012 und Dynamics 365 for Operations verwenden, um Unterklassen basierend auf Attributdekorationen zu instanziieren, was ein leicht erweiterbares Design einer Verarbeitungsklassenhierarchie ermöglicht.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
Die Informationen in diesem Beitrag basieren auf Dynamics AX 2012 R3. Sie können für andere Versionen gültig sein, müssen es aber nicht. (Update: Ich kann bestätigen, dass die Informationen in diesem Artikel auch für Dynamics 365 for Operations gültig sind.)
Beim Implementieren von Verarbeitungsklassen in Dynamics AX stehen Sie häufig vor der Aufgabe, eine Klassenhierarchie zu erstellen, in der jede Unterklasse einem Enumerationswert entspricht oder eine andere Datenkopplung aufweist. Ein klassisches Design besteht darin, dann eine Konstruktmethode in der Superklasse zu haben, die einen Schalter hat, der bestimmt, welche Klasse basierend auf der Eingabe instanziiert werden soll.
Dies funktioniert im Prinzip gut, aber wenn Sie viele verschiedene mögliche Eingaben haben (viele Elemente in einer Aufzählung oder vielleicht ist die Eingabe eine Kombination aus mehreren verschiedenen Werten), kann die Wartung mühsam und fehleranfällig werden und das Design hat immer den Nachteil, dass Sie die besagte Konstruktionsmethode ändern müssen, wenn Sie jemals eine neue Unterklasse hinzufügen oder Änderungen daran vornehmen, welche Unterklasse basierend auf welcher Eingabe verwendet werden soll.
Glücklicherweise gibt es hierfür eine viel elegantere, aber leider auch viel weniger bekannte Möglichkeit, nämlich die Verwendung des SysExtension-Frameworks.
Dieses Framework nutzt Attribute, mit denen Sie Ihre Unterklassen dekorieren können, damit das System erkennen kann, welche Unterklasse für welche Verarbeitung verwendet werden soll. Sie benötigen zwar immer noch eine Konstruktionsmethode, aber wenn Sie diese richtig ausführen, müssen Sie sie beim Hinzufügen neuer Unterklassen nie ändern.
Betrachten wir ein fiktives Beispiel und nehmen wir an, dass Sie eine Hierarchie implementieren, die eine Art Verarbeitung auf Grundlage der Tabelle InventTrans durchführt. Welche Verarbeitung durchgeführt wird, hängt von StatusReceipt und StatusIssue der Datensätze ab, sowie davon, ob die Datensätze mit SalesLine, PurchLine oder keinem von beiden verknüpft sind. Schon jetzt sehen Sie sich viele verschiedene Kombinationen an.
Nehmen wir an, Sie wissen, dass Sie derzeit nur eine Handvoll Kombinationen bewältigen müssen, Sie wissen aber auch, dass mit der Zeit immer mehr Kombinationen von Ihnen verlangt werden.
Lassen Sie es uns relativ einfach halten und sagen, dass Sie im Moment nur Datensätze verarbeiten müssen, die mit SalesLine in Zusammenhang stehen und einen StatusIssue von ReservPhysical oder ReservOrdered haben. Alle anderen Kombinationen können Sie im Moment ignorieren. Da Sie aber wissen, dass Sie sie später verarbeiten müssen, sollten Sie Ihren Code so gestalten, dass er leicht erweiterbar ist.
Ihre Hierarchie könnte zunächst etwa so aussehen:
- MeinProzessor
- MyProcessor_Sales
- MyProcessor_Sales_ReservOrdered
- MyProcessor_Sales_ReservPhysical
- MyProcessor_Sales
Nun könnten Sie problemlos eine Methode in der Superklasse implementieren, die eine Unterklasse basierend auf einem ModuleInventPurchSales und einem StatusIssue-Enum instanziiert. Sie müssen dann jedoch jedes Mal die Superklasse ändern, wenn Sie eine Unterklasse hinzufügen, und das entspricht nicht wirklich der Idee der Vererbung in der objektorientierten Programmierung. Schließlich müssen Sie RunBaseBatch oder SysOperationServiceBase nicht jedes Mal ändern, wenn Sie einen neuen Batch-Job hinzufügen.
Stattdessen können Sie das SysExtension-Framework verwenden. Dazu müssen Sie eine weitere Klasse hinzufügen, die SysAttribute erweitern muss. Diese Klasse wird als Attribut verwendet, mit dem Sie Ihre Verarbeitungsklassen dekorieren können.
Diese Klasse ähnelt stark der Art und Weise, wie Sie eine Datenvertragsklasse für eine SysOperation-Implementierung erstellen würden, da sie einige Datenmitglieder und Parm-Methoden zum Abrufen und Festlegen dieser Werte enthält.
In unserem Fall könnte die Klassendeklaration ungefähr so aussehen:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
Sie müssen eine neue()-Methode erstellen, um alle Datenelemente zu instanziieren. Wenn Sie möchten, können Sie einigen oder allen davon Standardwerte zuweisen, aber das habe ich nicht getan.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
Und Sie sollten auch eine Parm-Methode für jedes Datenelement implementieren, aber ich habe diese hier weggelassen, da ich sicher bin, dass Sie wissen, wie das geht – andernfalls betrachten wir es als eine Übung ;-)
Jetzt können Sie Ihre Attributklasse verwenden, um jede Ihrer Verarbeitungsklassen zu dekorieren. Die Klassendeklarationen könnten beispielsweise so aussehen:
StatusIssue::None,
StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservOrdered,
StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservPhysical,
StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}
Sie können Ihre Klassen natürlich beliebig benennen. Wichtig dabei ist, dass Sie Ihre Klassen mit Attributen versehen, die der Art der Verarbeitung entsprechen, die sie durchführen. (Denken Sie jedoch daran, dass es in Dynamics AX Namenskonventionen für Klassenhierarchien gibt und es immer eine gute Idee ist, diese nach Möglichkeit einzuhalten.)
Nachdem Sie Ihre Klassen nun so dekoriert haben, dass sie erkennen, welche Art von Verarbeitung jede von ihnen durchführt, können Sie das SysExtension-Framework nutzen, um nach Bedarf Objekte der Unterklassen zu instanziieren.
In Ihrer Superklasse (MyProcessor) könnten Sie eine Konstruktionsmethode wie diese hinzufügen:
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
MyProcessor ret;
MyProcessorSystemAttribute attribute;
;
attribute = new MyProcessorSystemAttribute( _module,
_statusIssue,
_statusReceipt);
ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);
if (!ret)
{
// no class found
// here you could throw an error, instantiate a default
// processor instead, or just do nothing, up to you
}
return ret;
}
Der wirklich interessante Teil – und eigentlich das Objekt (verzeihen Sie das Wortspiel) dieses gesamten Beitrags – ist die Methode getClassFromSysAttribute() in der Klasse SysExtensionAppClassFactory. Diese Methode akzeptiert den Namen der Superklasse einer Hierarchie (und diese Superklasse muss nicht an der Spitze der Hierarchie stehen; es bedeutet einfach, dass nur Klassen, die diese Klasse erweitern, in Frage kommen) und ein Attributobjekt.
Anschließend wird ein Objekt einer Klasse zurückgegeben, das die angegebene Superklasse erweitert und mit einem entsprechenden Attribut dekoriert ist.
Sie können der Konstruktionsmethode natürlich so viele weitere Validierungen oder Logik hinzufügen, wie Sie möchten. Wichtig ist jedoch, dass Sie diese Methode nach der Implementierung nie wieder ändern müssen. Sie können der Hierarchie Unterklassen hinzufügen, und solange Sie darauf achten, sie entsprechend zu dekorieren, wird die Konstruktionsmethode sie finden, auch wenn sie beim Schreiben noch nicht vorhanden waren.
Und wie steht es mit der Leistung? Ich habe ehrlich gesagt nicht versucht, einen Benchmark durchzuführen, aber mein Bauchgefühl sagt mir, dass die Leistung wahrscheinlich schlechter ist als die des klassischen Switch-Statement-Designs. Wenn man jedoch bedenkt, dass die meisten Leistungsprobleme in Dynamics AX durch den Datenbankzugriff verursacht werden, würde ich mir darüber keine allzu großen Sorgen machen.
Wenn Sie etwas implementieren, bei dem Tausende von Objekten schnell erstellt werden müssen, sollten Sie dies natürlich genauer untersuchen, aber in den klassischen Fällen, in denen Sie nur ein einzelnes Objekt instanziieren, um eine langwierige Verarbeitung durchzuführen, bezweifle ich, dass dies eine Rolle spielt. Wenn man meinen Tipp zur Fehlerbehebung (nächster Absatz) berücksichtigt, scheint das SysExtension-Framework auf Caching angewiesen zu sein, sodass ich bezweifle, dass es in einem laufenden System zu erheblichen Leistungseinbußen kommt. Fehlerbehebung: Wenn die Konstruktmethode Ihre Unterklassen nicht findet, obwohl Sie sicher sind, dass sie richtig dekoriert sind, kann es sich um ein Caching-Problem handeln. Versuchen Sie, die Caches auf Client und Server zu leeren. Ein Neustart des AOS sollte eigentlich nicht erforderlich sein, aber es kann der letzte Ausweg sein.