27 Kasım 2021 Cumartesi

Dynamics 365 F&O - If debugger begin jump meanless this would be about an endless recursive call

 I wrote an endless recursive call with AX 2012 accidentally. When I run my code I suspected an endless loop and tried to debug. Debugger was jumping from there by a confusing way over there in code meanless. I did a bit of google and found this would be about an endless recursive call.

When I lived same situation with 365 F&O two days ago it was so surprising for me. With all new compiled base in Visual Studio debugger I wouldn't expect this strange behaviour again. This time it was a bit different but same behaviour again (In 2009 it was going irrelevant code lines in every hit, this time in one point it was going irrelevant code again but after that begun an endless loop without a loop). This time I remembered what I lived with in 2009, so I fixed it fast.

At the end I mean if your debugger goes irrelevant code, -doesn't matter if 2009, 2012 or 365 F&O- you would be written an endless recursive call (Or your colleague would do a test in your DEV machine).


16 Eylül 2021 Perşembe

Dynamics 365 F&O - Extensions overview

 I worked a few AX upgrade projects. Unfortunately multi-layer architect is so painful with upgrade projects. Seem this problem is solved with extensions approach.

When you wanted to add fields/indexes/relations... in tables/data entities or controls in forms you create extensions directly:

 

 

Enums doesn't use numbers (at least in development environment) anymore. This's very good for upgrade projects. Unfortunately I lived problems about added elements without any gap to system enums. How it was a problem:

Sample, a system enum goes 1-2-3-4-5 and a developer added a new element. He/she actually should add new element as at least from 20 maybe 30 but added from 6. And in new version Microsoft added 6. :)

In the case of writing code there are some shortcuts. You can open related object and click right  mouse for copy template:


A sample class method extension:

 [PostHandlerFor(classStr(LedgerJournalCheckPost), methodStr(LedgerJournalCheckPost, run))]
    public static void LedgerJournalCheckPost_Post_run(XppPrePostArgs args)
    {
        LedgerJournalCheckPost      post = args.getThis() as                         LedgerJournalCheckPost;

        LedgerJournalTable          journalTable;
 

        journalTable = post.parmLedgerJournalTable();

        ...

    }

You can put this method in any class and this method will run after run method. If we selected Pre-event, it would be run before run.

In this case we extend ondeleted of a table:

 

 [DataEventHandler(tableStr(LedgerJournalTable), DataEventType::Deleted)]
    public static void LedgerJournalTable_onDeleted(Common sender,             DataEventArgs e)
    {

    LedgerJournalTable       ledgerJournalTable = sender as                     LedgerJournalTable;

    ...


If we want to add a new method of a table, things change a bit. We need a new class named  Table name and  _Extension word like this:

[ExtensionOf(tableStr(ABCRCreditTable))]
final class AGC_ABCRCreditTable_Extension
{
    public void updateDefaultDimension()
    {
            this.DefaultDimension = 11111;

    }

}

 

Or you can write additional code for a table method below in class up. You call chain methods with next keyword:

  void initfromCreditPosting()
    {
 

..... //your codes here

        next initfromCreditPosting();
    }

 

Access to modifiedfield event of a table:

[DataEventHandler(tableStr(ABCRCreditTable), DataEventType::ModifiedField)]
    public static void ABCRCreditTable_onModifiedField(Common sender, DataEventArgs e)
    {
        ABCRCreditTable         creditTable = sender as                             ABCRCreditTable;
        ModifyFieldEventArgs    eventArgs = e;
        int                     id = eventArgs.parmFieldId();
        switch(id)
        {
            case fieldNum(ABCRCreditTable,CreditAccount):
                if (ABCRParameters::find().CRCreditAccountType ==                         ABCRAccountType::Vend)
                    creditTable.updateDefaultDimension();
                break;
            case fieldNum(ABCRCreditTable,StrategyNumber):

...

We add extra control on LedgerJournalCheckPost  validate method:

 [PostHandlerFor(classStr(LedgerJournalCheckPost), methodStr(LedgerJournalCheckPost, validate))]
    public static void LedgerJournalCheckPost_Post_validate(XppPrePostArgs args)
    {
       LedgerJournalCheckPost   thisClass = args.getThis();
        LedgerJournalTable       ledgerJournalTable =     thisClass.ledgerJournalTable;

....

....

    if (abc)

    {

         args.setReturnValue(false);

         checkfailed("wrong value");

    }


}

 

Override form lookup method:

  [FormDataSourceEventHandler(formDataSourceStr(ABCRCreditTable, ABCRCreditTable), FormDataSourceEventType::Initialized)]
    public static void ABCRCreditTable_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
    {
        var overrides = new AGC_CreditTableExt();
        sender.object(fieldNum(ABCRCreditTable,                 TradeNumber)).registerOverrideMethod(methodStr(FormDataObject, lookup),
            methodStr(AGC_CreditTableExt, lookupTrade), overrides);
    
    } 

 

For call forms datasource from inside Lookup method  we need this red highlighted code:

   public void lookupStrategyInterCompany(FormControl _formControl)//, str _filterStr)
    {
        SysTableLookUp          sysTableLookUp = SysTableLookup::newParameters(tablenum(AGC_LoanStrategy),_formControl);
        Query                   query = new Query();
        QueryBuildDataSource    qbds = query.addDataSource(tablenum(AGC_LoanStrategy));
        FormObjectSet           dataSource =   _formControl.formRun().dataSource("ABCRCreditTable");
        ABCRCreditTable         creditTable = dataSource.cursor();


        query.allowCrossCompany(true);
        query.addCompanyRange(creditTable.CreditICCompany);
        qbds.orderMode(OrderMode::GroupBy);
        sysTableLookup.addLookupfield(fieldnum(AGC_LoanStrategy,StrategyNumber));
        sysTableLookup.parmQuery(query);
        sysTableLookup.performFormLookup();
    }

 

With this sample we make run code at up in forms data source OnInitialized event. We override datasource tradenumber fields lookUp method with  AGC_CreditTableExt class lookupTrade method.  You can write this code in any class. I choosed AGC_CreditTableExt .

Recently I learned another extension method for override lookups by Peter Ramer's blog by CancelSuperCall() .



ValidateField sample:

ValidateField method in table:

   public boolean validateField(FieldId _fieldIdToCheck)
    {
        CustTable            CustTable;
        VendTable            vendTable;
        boolean ret;
    
        ret = super(_fieldIdToCheck);
    
        switch(_fieldIdToCheck)
        {

....

....

....

        }

}


ValidateField extension which we put in class:


[PostHandlerFor(tableStr(ABCRCreditTable), tableMethodStr(ABCRCreditTable, validateField))]
    public static void ABCRCreditTable_Post_validateField(XppPrePostArgs args)
    {
        ABCRCreditTable     creditTable = args.getThis();
        FieldId             fieldId = args.getArg("_fieldIdToCheck");
        boolean             ret = args.getReturnValue();
        AGC_LoanStrategy    strategy;
        AGC_LoanTradeComb   trade;
 
        switch(fieldId)
        {
            case fieldNum(ABCRCreditTable, StrategyNumberInterCompany):
                if (creditTable.CreditICCompany != "")
                {
                    changecompany(creditTable.CreditICCompany)
                    {
                        select firstonly strategy
                            where strategy.StrategyNumber == creditTable.StrategyNumberInterCompany;
                        if (strategy.RecId == 0)
                        {
                            ret = checkFailed(strFmt("@SCM:TableFieldValidation",
                                    creditTable.StrategyNumberInterCompany,fieldPName(ABCRCreditTable,StrategyNumberInterCompany),
                                    tablePName(ABCRCreditTable)));
                        }
                    }
                }
                break;
           }
 
        args.setReturnValue(ret);
    }


Access datasource from form method post event:

 [PostHandlerFor(formStr(ABCRCreditTable), formMethodStr(ABCRCreditTable, setInterCompanyFields))]
    public static void ABCRCreditTable_Post_setInterCompanyFields(XppPrePostArgs args)
    {
        formrun o = args.getThis();
                   
        FormObjectSet   ds = o.dataSource("ABCRCreditTable");
        ABCRCreditTable creditTable = ds.cursor();
        ds.object(fieldNum(ABCRCreditTable,TradeNumberInterCompany)).allowEdit(creditTable.SourceOfLoan == AGC_SourceOfLoan::Intercompany);
        ds.object(fieldNum(ABCRCreditTable,StrategyNumberInterCompany)).allowEdit(creditTable.SourceOfLoan == AGC_SourceOfLoan::Intercompany);
       
    }

 



Adding a display method to a form:

[ExtensionOf(formStr(SalesAgreement))]
final class AGC_SalesAgreement_Extension
{
    display Amount dispRemainAmount()
    {     
       FormRealControl n = this.design().controlName("NetAmount");
       FormRealControl a = this.design().controlName("agreementRemainingRelease");
       
        return n.realValue() * a.realValue();
    }

}

Than create a form extension. When added a control compatible with display method (for this sample a real control) datamethod combobox will show this display method.