Bruke SysExtension Framework for å finne ut hvilken underklasse som skal instansieres i Dynamics AX 2012
Publisert: 16. februar 2025 kl. 00:25:52 UTC
Denne artikkelen beskriver hvordan du bruker det lite kjente SysExtension-rammeverket i Dynamics AX 2012 og Dynamics 365 for Operations for å instansiere underklasser basert på attributtdekorasjoner, noe som muliggjør en lett utvidbar utforming av et prosesseringsklassehierarki.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
Informasjonen i dette innlegget er basert på Dynamics AX 2012 R3. Det kan være eller ikke være gyldig for andre versjoner. (Oppdatering: Jeg kan bekrefte at informasjonen i denne artikkelen også er gyldig for Dynamics 365 for Operations)
Når du implementerer behandlingsklasser i Dynamics AX, står du ofte overfor å lage et klassehierarki der hver underklasse tilsvarer en enum-verdi eller har en annen datakobling. Et klassisk design er å da ha en konstruksjonsmetode i superklassen, som har en bryter som bestemmer hvilken klasse som skal instansieres basert på input.
Dette fungerer i prinsippet bra, men hvis du har mange forskjellige mulige innganger (mange elementer i en enum eller kanskje inngangen er en kombinasjon av flere forskjellige verdier), kan det bli kjedelig og feilutsatt å vedlikeholde og designet har alltid den ulempen at du må endre konstruksjonsmetoden hvis du noen gang legger til en ny underklasse eller gjør endringer i hvilken underklasse som skal brukes basert på hvilken inngang.
Heldigvis finnes det en mye mer elegant, men dessverre også mye mindre kjent måte å gjøre dette på, nemlig ved bruk av SysExtension-rammeverket.
Dette rammeverket utnytter attributter som du kan bruke til å dekorere underklassene dine for å gjøre systemet i stand til å finne ut hvilken underklasse som skal brukes til å håndtere hva. Du vil fortsatt trenge en konstruksjonsmetode, men hvis den gjøres riktig, trenger du aldri å endre den når du legger til nye underklasser.
La oss se på et tenkt eksempel og si at du skal implementere et hierarki som utfører en slags prosessering basert på InventTrans-tabellen. Hvilken behandling som skal gjøres avhenger av StatusReceipt og StatusIssue for postene, samt om postene er relatert til SalesLine, PurchLine eller ingen av dem. Allerede nå ser du på mange forskjellige kombinasjoner.
La oss da si at du vet at du foreløpig bare trenger å håndtere en håndfull av kombinasjonene, men du vet også at du vil bli bedt om å kunne håndtere flere og flere kombinasjoner over tid.
La oss holde det relativt enkelt og si at foreløpig trenger du bare å håndtere poster relatert til SalesLine med en StatusIssue av ReservPhysical eller ReservOrdered, alle andre kombinasjoner kan ignoreres foreløpig, men siden du vet at du må håndtere dem senere, vil du designe koden din på en måte som gjør den lett å utvide.
Hierarkiet ditt kan se omtrent slik ut foreløpig:
- Min prosessor
- MyProcessor_Salg
- MyProcessor_Sales_ReservOrdered
- MyProcessor_Sales_ReservPhysical
- MyProcessor_Salg
Nå kan du enkelt implementere en metode i superklassen som instansierer en underklasse basert på en ModuleInventPurchSales og en StatusIssue enum. Men du må da endre superklassen hver gang du legger til en underklasse, og det er egentlig ikke ideen med arv i objektorientert programmering. Du trenger tross alt ikke å endre RunBaseBatch eller SysOperationServiceBase hver gang du legger til en ny batchjobb.
I stedet kan du bruke SysExtension-rammeverket. Det vil kreve at du legger til en annen klasse, som må utvide SysAttribute. Denne klassen vil bli brukt som attributtet du kan dekorere behandlingsklassene dine med.
Denne klassen er veldig lik hvordan du vil lage en datakontraktsklasse for en SysOperation-implementering, ved at den vil ha noen datamedlemmer og parm-metoder for å hente og sette disse verdiene.
I vårt tilfelle kan ClassDeclaration se omtrent slik ut:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
Du må lage en new() metode for å instansiere alle datamedlemmer. Hvis du ønsker kan du gi noen eller alle av dem standardverdier, men jeg har ikke gjort det.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
Og du bør også implementere en parm-metode for hvert datamedlem, men jeg har utelatt dem her, da jeg er sikker på at du vet hvordan du gjør det - ellers, la oss se på det som en øvelse ;-)
Nå kan du bruke attributtklassen din til å dekorere hver av behandlingsklassene dine. For eksempel kan klasseerklæringene se slik ut:
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 selvfølgelig navngi klassene dine slik du vil, den viktige delen her er at du dekorerer klassene dine med attributter som tilsvarer hva slags behandling de gjør. (Men husk at det er navnekonvensjoner for klassehierarkier i Dynamics AX, og det er alltid en god idé å følge disse, hvis mulig).
Nå som du har dekorert klassene dine for å identifisere hva slags behandling hver av dem gjør, kan du dra nytte av SysExtension-rammeverket for å instansiere objekter til underklassene etter behov.
I superklassen din (MyProcessor) kan du legge til en konstruksjonsmetode som denne:
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 virkelig interessante delen - og egentlig objektet (unnskyld ordspillet) i hele dette innlegget - er getClassFromSysAttribute()-metoden i SysExtensionAppClassFactory-klassen. Det denne metoden gjør er at den aksepterer navnet på superklassen til et hierarki (og denne superklassen trenger ikke å være på toppen av hierarkiet; det betyr ganske enkelt at bare klasser som utvider denne klassen vil være kvalifisert) og et attributtobjekt.
Den returnerer deretter et objekt av en klasse som utvider den spesifiserte superklassen og er dekorert med et tilsvarende attributt.
Du kan selvsagt legge til så mye ytterligere validering eller logikk til konstruksjonsmetoden du ønsker, men det viktige her er at når den er implementert, bør du aldri måtte endre denne metoden igjen. Du kan legge til underklasser i hierarkiet, og så lenge du sørger for å dekorere dem riktig, vil konstruksjonsmetoden finne dem selv om de ikke eksisterte da den ble skrevet.
Hva med ytelsen? Jeg har ærlig talt ikke forsøkt å benchmarke det, men magefølelsen min er at dette sannsynligvis fungerer dårligere enn det klassiske switch statement-designet. Men med tanke på at de aller fleste ytelsesproblemene i Dynamics AX er forårsaket av databasetilgang, ville jeg ikke bekymre meg for mye om det.
Selvfølgelig, hvis du implementerer noe som vil kreve at tusenvis av objekter opprettes raskt, kan det være lurt å undersøke videre, men i de klassiske tilfellene der du bare instansierer et enkelt objekt for å gjøre noe langvarig prosessering, tviler jeg på at det vil ha noen betydning. Med tanke på feilsøkingstipset mitt (neste avsnitt), ser det ut til at SysExtension-rammeverket er avhengig av caching, så i et kjørende system tviler jeg på at det har en betydelig ytelsestreff. Feilsøking: Hvis konstruksjonsmetoden ikke finner underklassene dine selv om du er sikker på at de er riktig dekorert, kan det være et cachingproblem. Prøv å tømme cacher på både klient og server. Det burde ikke være nødvendig å starte AOS på nytt, men det kan være siste utvei.