Miklix

Brug af SysExtension Framework til at finde ud af, hvilken underklasse der skal instantieres i Dynamics AX 2012

Udgivet: 16. februar 2025 kl. 00.25.36 UTC
Sidst opdateret: 12. januar 2026 kl. 08.42.54 UTC

Denne artikel beskriver, hvordan man bruger det mindre kendte SysExtension-framework i Dynamics AX 2012 og Dynamics 365 for Operations til at instantiere underklasser baseret på attributdekorationer, hvilket muliggør et let udvideligt design af et behandlingsklassehierarki.


Denne side er blevet maskinoversat fra engelsk for at gøre den tilgængelig for så mange mennesker som muligt. Desværre er maskinoversættelse endnu ikke en perfekt teknologi, så der kan forekomme fejl. Hvis du foretrækker det, kan du se den originale engelske version her:

Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012

Oplysningerne i dette indlæg er baseret på Dynamics AX 2012 R3. De er muligvis ikke gyldige for andre versioner. (Opdatering: Jeg kan bekræfte, at oplysningerne i denne artikel også er gyldige for Dynamics 365 for Operations)

Når man implementerer behandlingsklasser i Dynamics AX, står man ofte over for at oprette et klassehierarki, hvor hver underklasse svarer til en enum-værdi eller har en anden datakobling. Et klassisk design er derefter at have en construct-metode i superklassen, som har en switch, der bestemmer, hvilken klasse der skal instantieres baseret på inputtet.

Dette fungerer godt i princippet, men hvis du har mange forskellige mulige input (mange elementer i en enum eller måske er inputtet en kombination af flere forskellige værdier), kan det blive besværligt og fejlbehæftet at vedligeholde, og designet har altid den ulempe, at du bliver nødt til at ændre nævnte konstruktionsmetode, hvis du nogensinde tilføjer en ny underklasse eller foretager ændringer i, hvilken underklasse der skal bruges baseret på hvilket input.

Heldigvis findes der en langt mere elegant, men desværre også langt mindre kendt, måde at gøre dette på, nemlig ved hjælp af SysExtension-frameworket.

Dette framework udnytter attributter, som du kan bruge til at dekorere dine underklasser, så systemet kan finde ud af, hvilken underklasse der skal bruges til at håndtere hvad. Du skal stadig bruge en konstruktionsmetode, men hvis den gøres korrekt, behøver du aldrig at ændre den, når du tilføjer nye underklasser.

Lad os se på et imaginært eksempel og sige, at du skal implementere et hierarki, der udfører en form for behandling baseret på InventTrans-tabellen. Hvilken behandling der skal udføres, afhænger af StatusReceipt og StatusIssue for posterne, samt om posterne er relateret til SalesLine, PurchLine eller ingen af delene. Allerede nu ser du på mange forskellige kombinationer.

Lad os så sige, at du ved, at du for nu kun behøver at håndtere en håndfuld af kombinationerne, men du ved også, at du vil blive bedt om at kunne håndtere flere og flere kombinationer over tid.

Lad os holde det relativt simpelt og sige, at du for nu kun behøver at håndtere poster relateret til SalesLine med en StatusIssue på ReservPhysical eller ReservOrdered. Alle andre kombinationer kan ignoreres for nu, men da du ved, at du skal håndtere dem senere, bør du designe din kode på en måde, der gør den let udvidelig.

Dit hierarki kan se nogenlunde sådan ud for nu:

  • MinProcessorMinProcessor_SalgMinProcessor_SalgsreserveBestiltMinProcessor_SalgsreserveFysisk

Nu kan du nemt implementere en metode i superklassen, der instansierer en underklasse baseret på en ModuleInventPurchSales og en StatusIssue-enum. Men du skal derefter ændre superklassen hver gang du tilføjer en underklasse, og det er ikke rigtigt ideen bag arv i objektorienteret programmering. Du behøver trods alt ikke at ændre RunBaseBatch eller SysOperationServiceBase hver gang du tilføjer et nyt batchjob.

Stedet kan du bruge SysExtension-frameworket. Det kræver, at du tilføjer en anden klasse, som skal udvide SysAttribute. Denne klasse vil blive brugt som den attribut, du kan dekorere dine behandlingsklasser med.

Denne klasse minder meget om, hvordan du ville oprette en datakontraktklasse til en SysOperation-implementering, idet den vil have nogle datamedlemmer og parm-metoder til at hente og indstille disse værdier.

I vores tilfælde kan ClassDeclaration se nogenlunde sådan ud:

class MyProcessorSystemAttribute extends SysAttribute
{
    ModuleInventPurchSales  module;
    StatusIssue             statusIssue;
    StatusReceipt           statusReceipt
}

Du skal lave en new() metode til at instansiere alle datamedlemmer. Hvis du ønsker det, kan du give nogle eller alle af dem standardværdier, men det har jeg ikke gjort før.

public void new(ModuleInventPurchSales  _module,
                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 udeladt dem her, da jeg er sikker på, at du ved, hvordan man gør det - ellers lad os betragte det som en øvelse ;-)

Nu kan du bruge din attributklasse til at dekorere hver af dine behandlingsklasser. For eksempel kan klassedeklarationerne se sådan ud:

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            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 navngive dine klasser, præcis som du vil. Det vigtige her er, at du dekorerer dine klasser med attributter, der svarer til den type behandling, de udfører. (Men husk, at der er navngivningskonventioner for klassehierarkier i Dynamics AX, og det er altid en god idé at følge dem, hvis det er muligt).

Nu hvor du har dekoreret dine klasser for at identificere, hvilken type behandling hver af dem udfører, kan du udnytte SysExtension-frameworket til at instantiere objekter i underklasserne efter behov.

I din superklasse (MyProcessor) kan du tilføje en konstruktionsmetode som denne:

public static MyProcessor construct(ModuleInventPurchSales _module,
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 del - og i virkeligheden objektet (undskyld ordspillet) i hele dette indlæg - er getClassFromSysAttribute()-metoden i SysExtensionAppClassFactory-klassen. Metoden accepterer navnet på superklassen i et hierarki (og denne superklasse behøver ikke at være øverst i hierarkiet; det betyder blot, at kun klasser, der udvider denne klasse, vil være berettigede) og et attributobjekt.

Den returnerer derefter et objekt fra en klasse, der udvider den angivne superklasse og er dekoreret med en tilsvarende attribut.

Du kan naturligvis tilføje så meget yderligere validering eller logik til konstruktionsmetoden, som du ønsker, men den vigtige konklusion her er, at når den først er implementeret, burde du aldrig skulle ændre denne metode igen. Du kan tilføje underklasser til hierarkiet, og så længe du sørger for at dekorere dem korrekt, vil konstruktionsmetoden finde dem, selvom de ikke eksisterede, da den blev skrevet.

Hvad med ydeevnen? Jeg har ærligt talt ikke forsøgt at benchmarke det, men min mavefornemmelse siger, at dette sandsynligvis yder dårligere end det klassiske switch-sætningsdesign. Men i betragtning af at langt de fleste ydeevneproblemer i Dynamics AX skyldes databaseadgang, ville jeg ikke bekymre mig for meget om det.

Hvis du implementerer noget, der kræver, at tusindvis af objekter oprettes hurtigt, kan du selvfølgelig undersøge det nærmere, men i de klassiske tilfælde, hvor du blot instantierer et enkelt objekt for at udføre en længerevarende behandling, tvivler jeg på, at det vil have nogen betydning. I betragtning af mit fejlfindingstip (næste afsnit) ser det også ud til, at SysExtension-frameworket er afhængigt af caching, så i et kørende system tvivler jeg på, at det har et betydeligt ydeevnetab.

Fejlfinding: Hvis construct-metoden ikke finder dine underklasser, selvom du er sikker på, at de er dekoreret korrekt, kan det være et caching-problem. Prøv at rydde caches på både klient og server. Det burde ikke være nødvendigt rent faktisk at genstarte AOS, men det kan være sidste udvej.

Yderligere læsning

Hvis du kunne lide dette indlæg, kan du måske også lide disse forslag:


Del på BlueskyDel på FacebookDel på LinkedInDel på TumblrDel på XDel på LinkedInFastgør på Pinterest

Mikkel Christensen

Om forfatteren

Mikkel Christensen
Mikkel er skaberen og ejeren af miklix.com. Han har over 20 års erfaring som professionel computerprogrammør/softwareudvikler og er i øjeblikket fuldtidsansat i en stor europæisk IT-virksomhed. Når han ikke blogger, bruger han sin fritid på en lang række interesser, hobbyer og aktiviteter, som i et vist omfang afspejles i de mange forskellige emner, der dækkes på dette websted.