Använda SysExtension Framework för att ta reda på vilken underklass som ska instansieras i Dynamics AX 2012
Publicerad: 16 februari 2025 kl. 00:26:00 UTC
Den här artikeln beskriver hur man använder det föga kända ramverket SysExtension i Dynamics AX 2012 och Dynamics 365 for Operations för att instansiera underklasser baserade på attributdekorationer, vilket möjliggör en lätt utbyggbar design av en bearbetningsklasshierarki.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
Informationen i det här inlägget är baserad på Dynamics AX 2012 R3. Det kan eller kanske inte är giltigt för andra versioner. (Uppdatering: Jag kan bekräfta att informationen i den här artikeln också är giltig för Dynamics 365 for Operations)
När du implementerar bearbetningsklasser i Dynamics AX ställs du ofta inför att skapa en klasshierarki där varje underklass motsvarar ett enumvärde eller har någon annan datakoppling. En klassisk design är att sedan ha en konstruktionsmetod i superklassen, som har en switch som bestämmer vilken klass som ska instansieras utifrån ingången.
Detta fungerar i princip bra, men om du har många olika möjliga ingångar (många element i en enum eller kanske inmatningen är en kombination av flera olika värden) kan det bli tråkigt och felbenäget att underhålla och designen har alltid nackdelen att du kommer behöva modifiera nämnda konstruktionsmetod om du någonsin lägger till en ny underklass eller gör ändringar i vilken underklass som ska användas baserat på vilken ingång.
Lyckligtvis finns det ett mycket mer elegant, men tyvärr också mycket mindre känt, sätt att göra detta på, nämligen genom att använda ramverket SysExtension.
Detta ramverk utnyttjar attribut som du kan använda för att dekorera dina underklasser för att göra systemet i stånd att ta reda på vilken underklass som ska användas för att hantera vad. Du kommer fortfarande att behöva en konstruktionsmetod, men om den görs på rätt sätt behöver du aldrig ändra den när du lägger till nya underklasser.
Låt oss titta på ett tänkt exempel och säga att du kommer att implementera en hierarki som gör någon form av bearbetning baserat på InventTrans-tabellen. Vilken bearbetning som ska göras beror på StatusReceipt och StatusIssue för posterna, samt på om posterna är relaterade till SalesLine, PurchLine eller ingetdera. Redan nu tittar du på många olika kombinationer.
Låt oss då säga att du vet att du för närvarande bara behöver hantera en handfull av kombinationerna, men du vet också att du kommer att bli ombedd att kunna hantera fler och fler kombinationer med tiden.
Låt oss hålla det relativt enkelt och säga att för närvarande behöver du bara hantera poster relaterade till SalesLine med ett StatusIssue av ReservPhysical eller ReservOrdered, alla andra kombinationer kan ignoreras för tillfället, men eftersom du vet att du kommer att behöva hantera dem senare, kommer du att vilja designa din kod på ett sätt som gör den lätt att utöka.
Din hierarki kan se ut ungefär så här för tillfället:
- MyProcessor
- MyProcessor_Sales
- MyProcessor_Sales_ReservOrdered
- MyProcessor_Sales_ReservPhysical
- MyProcessor_Sales
Nu kan du enkelt implementera en metod i superklassen som instansierar en underklass baserad på en ModuleInventPurchSales och en StatusIssue enum. Men du kommer då att behöva modifiera superklassen varje gång du lägger till en underklass, och det är egentligen inte tanken med nedärvning i objektorienterad programmering. När allt kommer omkring behöver du inte ändra RunBaseBatch eller SysOperationServiceBase varje gång du lägger till ett nytt batchjobb.
Istället kan du använda ramverket SysExtension. Det kräver att du lägger till en annan klass, som behöver utöka SysAttribute. Den här klassen kommer att användas som attributet du kan dekorera dina bearbetningsklasser med.
Den här klassen är mycket lik hur du skulle skapa en datakontraktsklass för en SysOperation-implementering, eftersom den kommer att ha några datamedlemmar och parm-metoder för att hämta och ställa in dessa värden.
I vårt fall kan ClassDeclaration se ut ungefär så här:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
Du måste skapa en new()-metod för att instansiera alla datamedlemmar. Om du vill kan du ge några eller alla standardvärden, men det har jag inte gjort.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
Och du bör också implementera en parm-metod för varje datamedlem, men jag har utelämnat dem här eftersom jag är säker på att du vet hur man gör det - annars, låt oss betrakta det som en övning ;-)
Nu kan du använda din attributklass för att dekorera var och en av dina bearbetningsklasser. Till exempel kan klassdeklarationerna se ut så här:
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
{
}
Du kan givetvis namnge dina klasser hur du vill, det viktiga här är att du dekorerar dina klasser med attribut som motsvarar vilken typ av bearbetning de gör. (Men kom ihåg att det finns namnkonventioner för klasshierarkier i Dynamics AX och det är alltid en bra idé att följa dem, om möjligt).
Nu när du har dekorerat dina klasser för att identifiera vilken typ av bearbetning var och en av dem gör, kan du dra fördel av SysExtension-ramverket för att instansiera objekt från underklasserna efter behov.
I din superklass (MyProcessor) kan du lägga till en konstruktionsmetod så här:
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;
}
Den riktigt intressanta delen - och egentligen objektet (ursäkta ordleken) i hela detta inlägg - är metoden getClassFromSysAttribute() i klassen SysExtensionAppClassFactory. Vad den här metoden gör är att den accepterar namnet på superklassen i en hierarki (och den här superklassen behöver inte vara överst i hierarkin, det betyder helt enkelt att endast klasser som utökar den här klassen kommer att vara berättigade) och ett attributobjekt.
Den returnerar sedan ett objekt av en klass som utökar den angivna superklassen och är dekorerad med ett motsvarande attribut.
Du kan självklart lägga till så mycket ytterligare validering eller logik till konstruktionsmetoden som du vill, men det viktiga här är att när den väl har implementerats ska du aldrig behöva ändra den här metoden igen. Du kan lägga till underklasser till hierarkin och så länge du ser till att dekorera dem på rätt sätt, kommer konstruktionsmetoden att hitta dem även om de inte fanns när den skrevs.
Hur är det med prestanda? Jag har ärligt talat inte försökt att benchmarka det, men min magkänsla är att detta förmodligen presterar sämre än den klassiska switch statement-designen. Men med tanke på att överlägset de flesta prestandaproblem i Dynamics AX orsakas av databasåtkomst, skulle jag inte oroa mig för mycket om det.
Naturligtvis, om du implementerar något som kräver att tusentals objekt skapas snabbt, kanske du vill undersöka ytterligare, men i de klassiska fallen där du bara instansierar ett enstaka objekt för att göra en långvarig bearbetning, tvivlar jag på att det kommer att spela någon roll. Med tanke på mitt felsökningstips (nästa stycke) verkar det som om SysExtension-ramverket förlitar sig på cachning, så i ett körande system tvivlar jag på att det har en betydande prestandaträff. Felsökning: Om konstruktionsmetoden inte hittar dina underklasser trots att du är säker på att de är korrekt dekorerade kan det vara ett cachningsproblem. Testa att rensa cacheminne på både klient och server. Det borde inte vara nödvändigt att faktiskt starta om AOS, men det kan vara sista utvägen.