Wednesday, February 26, 2020

get class Name of a static method

in different occasions especially while using the SysExtension or SysOperation frameworks I found myself writing again the same code and I wanted something more generic the get the current class name. I didn’t find any built in method so I decided to make this simple helper method:

public static ClassName getClassNameFromFuncName(str _funcName)
{
    ClassName className;
    #define.ObjectMethodSeparator('.')
    #define.StaticMethodSeparator('::')
    
    if (strContains(_funcName, #ObjectMethodSeparator))
        className = any2str(conPeek(str2con(_funcName, #ObjectMethodSeparator), 1));
    
    if (strContains(_funcName, #StaticMethodSeparator))
        className = any2str(conPeek(str2con(_funcName, #StaticMethodSeparator), 1));
    
    if (!className2Id(className))
        throw(error(Error::wrongUseOfFunction(funcName())));
    
    return className;    
}

you can try it:

public static void main(Args _args)
{
    str fullName = funcName();
    
    str className = MYD_Functions::getClassNameFromFuncName(fullName);

    info(className);
}

here is how I used it to make a constructor more generic and reusable in other SysExtension instances:

class EF_ExtFrameworkSample3bisFactory extends EF_ExtFrameworkSample3bisBaseClass
{
}

public static Object construct(str _className, str _mySetting = '')
{
    EF_StringAtttibute attr;
    Object baseCl;
    ClassName factoryClassName;
    ClassId factoryClassId;
    ClassName baseClassName;
    ClassId baseClassId;
        
    factoryClassName = MYD_Functions::getClassNameFromFuncName(funcName());
    factoryClassId = className2Id(factoryClassName);
    baseClassId = SysDictClass::superClass(factoryClassId);
    baseClassName = classId2Name(baseClassId);

    attr = new EF_StringAtttibute(_className);
    baseCl = SysExtensionAppClassFactory::getClassFromSysAttribute(baseClassName, attr);

    if (!baseCl)
    {
    throw error(Error::wrongUseOfFunction(funcName()));
    }

    baseCl.initSetting(_mySetting);

    return baseCl;

}

Monday, February 24, 2020

Checks for duplicate entities that have the same names

there are different forms where we want to perform a check for duplicate entry names.An example is:

\Forms\HcmPersonalContactNew\Methods\checkDuplicateName

which is using this static method:

\Classes\DirUtility\checkDuplicate

the problem with this method is that it works only in a form which has as datasource a DirParty entity. In order to be able to use the same code also in other contexts I slightly modified the code like this:

public static boolean checkDirPartyDuplicate(Common _nameRecord, DirPartyType _partyType, str _entityName)
{
    //Enrico: I adapted the code from \Classes\DirUtility\checkDuplicate to use it also when not in a form
    
    Common common;
    DictTable partyDicttable;

    FormRun formRun;
    Args    args;
    Object  formObject;
    Common  nameRecord;
    FormDataSource  fds;

    args = new Args(formStr(DirPartyVerification));
    args.record(_nameRecord);
    args.parmEnumType(enumNum(DirPartyType));
    args.parmEnum(_partyType);
    args.parm(_entityName);
    formRun = classfactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.wait();


    if (formRun.closedOk() && formHasMethod(formRun, identifierStr(getName)))
    {
        formObject = formRun;
        nameRecord = formObject.getName();

        if (nameRecord.RecId)
        {
            partyDicttable = new SysDictTable(nameRecord.TableId);
            if(partyDicttable)
            {
                common = partyDicttable.makeRecord();
            }
            //we reselect the party since quick create wich uses this code path requires non pessimistic lock selection
            select common where common.RecId == nameRecord.RecId;
            _nameRecord.data(common);
            
        }
    }
    else
    {
        return false;
    }

    return true;
}


now can be used like this:

static void MYD_Functions_checkDirPartyDuplicate(Args _args)
{
    DirPersonName dirPersonName;
    boolean nameChecked = true;
    
    dirPersonName.FirstName = 'enrico';
    dirPersonName.LastName = 'fuchs';
    
    if (DirParameters::find().UseDuplicateCheck == NoYes::Yes
            && DirPersonName::nameLikeCount(dirPersonName.FirstName, dirPersonName.MiddleName, dirPersonName.LastName) > 0)
        {
            nameChecked = MYD_Functions::checkDirPartyDuplicate(dirPersonName, DirPartyType::Person, tableStr(DirPerson));
        }
    
    if (nameChecked)
        info(strFmt('%1', dirPersonName.RecId));
}

Monday, February 17, 2020

Extensions: Extend a macro without overlay - pack/unpack

I have a class that implements SysPackable. When I extend this class I want also to extend the list of variables in the macro.

Note: in Ax 2012 you don’t have SysPackExtensions.
otherwise see here: https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/extend-runbase-class

class EF_SysPackableTrial implements SysPackable
{
    Description description;
    str myString;

    #define.CurrentVersion(1)
    #define.version1(1)
    #localmacro.CurrentList
    description,
    myString
    #endmacro
}

public container pack()
{
    return [#CurrentVersion, #CurrentList];
}

public boolean unpack(container packedClass)
{
    int version     = runbase::getVersion(packedClass);

    switch (version)
    {
        case #CurrentVersion:
            [version,#CurrentList] = packedClass;
            return true;
        default :
            return false;
    }

    return false;
}

public static EF_SysPackableTrial create(container _pack)
{
    EF_SysPackableTrial cl = new EF_SysPackableTrial();

    cl.unpack(_pack);
    return cl;
}

public Description parmDescription(Description _description = description)
{
    description = _description;

    return description;
}

public str parmMyString(str _myString = myString)
{
    myString = _myString;

    return myString;
}

in the extend class I modified the pack and unpack methods

class EF_SysPackableTrialExtend extends EF_SysPackableTrial
{
    Name name;

    #define.CurrentVersion(1)
    #define.version1(1)
    #localmacro.CurrentList1
    name
    #endmacro
}

public container pack()
{
    container ret;
    
    ret = [#CurrentVersion, [#CurrentVersion, #CurrentList], [#CurrentVersion, #CurrentList1]];
    
    return ret;
}

public boolean unpack(container packedClass)
{
    int version = SysOperationHelper::getVersion(packedClass);

    switch (version)
    {
        case #CurrentVersion:
            [version,#CurrentList] = conPeek(packedClass,2);
            [version,#CurrentList1] = conPeek(packedClass,3);
            return true;
        default :
            return false;
    }

    return false;
}

public static EF_SysPackableTrialExtend create(container _pack)
{
    EF_SysPackableTrialExtend cl = new EF_SysPackableTrialExtend();

    cl.unpack(_pack);
    return cl;
}

public Name parmName(Name _name = name)
{
    name = _name;

    return name;
}

and here a simple job to prove it works fine:

static void EF_SysPackableTrialExtend(Args _args)
{
    EF_SysPackableTrialExtend EF_SysPackableTrialExtend, sysPackableTrial1;

    EF_SysPackableTrialExtend = new EF_SysPackableTrialExtend();
    EF_SysPackableTrialExtend.parmDescription('ciao1');
    EF_SysPackableTrialExtend.parmMyString('ciao2');
    EF_SysPackableTrialExtend.parmName('pp');

    sysPackableTrial1 = EF_SysPackableTrialExtend::create(EF_SysPackableTrialExtend.pack());

    info(sysPackableTrial1.parmDescription());
    info(sysPackableTrial1.parmMyString());
    info(sysPackableTrial1.parmName());
}

I posted this sample also on the forum https://community.dynamics.com/ax/f/microsoft-dynamics-ax-forum/380610/extensions-extend-a-macro-without-overlay---pack-unpack-in-ax-2012

Thursday, February 13, 2020

packed class and Table InMemory Serializer run on server

if you look this class \Classes\HcmPositionCopy follow a pattern to run on server the heavier process.

when I tried to make a similar code for my code I had a problem with an inMemory temp table I wanted to pass as parameter. Here my code how I solved using SysTableInMemorySerializer class.

image

class EF_ClassPack_Trial1
{
    //same template as \Classes\HcmPositionCopy
    
    //input parameters
    Name name;
    EF_ClassPackTmp_Trial1 tmp;
    PackedTable packedTmp;
    
    //output parameters
    Description descr;
    
    ParametersX packedClass;
    
    #define.CurrentVersion(3)
    #localmacro.CurrentList
    
    name,
    tmp,
    descr,
    packedTmp
    
    #endmacro
}

protected static EF_ClassPack_Trial1 construct()
{
    return new EF_ClassPack_Trial1();
}

public static EF_ClassPack_Trial1 newTrial()
{
    EF_ClassPack_Trial1 cl;
    
    cl = EF_ClassPack_Trial1::construct();
    
    return cl;
}

public Name parmName(Name _name = name)
{
    name = _name;

    return name;
}

public EF_ClassPackTmp_Trial1 parmTmp(EF_ClassPackTmp_Trial1 _tmp = tmp)
{
    EF_ClassPackTmp_Trial1 tmp1;
    
    if (prmisDefault(_tmp))
        SysTableInMemorySerializer::unpackTable(packedTmp, tmp1); 
    else    
        packedTmp = SysTableInMemorySerializer::packTable(_tmp);
            
    return tmp1;    
}

public Description parmDescr(Description _descr = descr)
{
    descr = _descr;

    return descr;
}

public void run()
{
    startLengthyOperation();
    
    packedClass = EF_ClassPack_Trial1::runOnServer(this.pack());
    
    endLengthyOperation();
}

public client server static EF_ClassPack_Trial1 create(container _packedClass)
{
    EF_ClassPack_Trial1 cl = EF_ClassPack_Trial1::construct();

    cl.unpack(_packedClass);

    return cl;
}

public container pack()
{
    return [#CurrentVersion, #CurrentList];
}

public boolean unpack(container _packedClass)
{
    Integer version = conPeek(_packedClass,1);

    switch (version)
    {
        case #CurrentVersion:
            [version, #CurrentList] = _packedClass;
            break;
        default:
            return false;
    }

    return true;
}

public EF_ClassPack_Trial1 getRunOutput()
{
    return EF_ClassPack_Trial1::create(packedClass); 
}

public static server container runOnServer(container _packedClass)
{
    EF_ClassPack_Trial1 cl = EF_ClassPack_Trial1::create(_packedClass);
    cl.doSomeOperation();
    
    return cl.pack();
}

private void doSomeOperation()
{
    Counter tmpCount;
    
    tmp = this.parmTmp();
    
    select count(RecId) from tmp;
    tmpCount = int642int(tmp.RecId);
    
    descr = strFmt('ciao %1 - tot: %2', name, tmpCount);    
}

public static void main(Args _args)
{
    EF_ClassPack_Trial1 cl;
    //EF_ClassPack_Trial1 clOptput;
    EF_ClassPackTmp_Trial1 tmp;
    
    tmp.Field1 = 'ciao';
    tmp.insert();
    
    tmp.Field1 = 'ciao1';
    tmp.insert();
    
    cl = EF_ClassPack_Trial1::newTrial();
    cl.parmName('Giorgio');
    cl.parmTmp(tmp);
    cl.run();
    
    //clOptput = cl.getRunOutput();
    //info(clOptput.parmDescr());
    
    info(cl.getRunOutput().parmDescr());
}

Tuesday, January 21, 2020

Date effective child form in Dynamics 2012

in a form to display the date effective filter pane you have to add this snippet in the form init method:

DateEffectivenessPaneController::constructWithForm(
        this,
        MYD_ClinicianPositions_ds);

the DateEffectivenessPaneController is responsible of creating all the user interface and the interaction.

image

if you also have a child form which is also date effective and you want it also to be filtered based on the date effective selection on the parent form you can do this:

public class FormRun extends ObjectRun
{
    SysFormSplitter_X verticalSplitter;
    DateEffectivenessPaneController effectivenessPaneController;
}

public void init()
{
    super();
    //Initialize splitter
    verticalSplitter = new SysFormSplitter_X(VSplitter, GridContainer, element, 300);

    //initialize the DateEffectivenessPaneController
    effectivenessPaneController = DateEffectivenessPaneController::constructWithForm(this, MYD_Mentor_ds);
}

then override the executeQuery method of the subform darasource:

public void executeQuery()
{
    FromDate fromDate = dateNull();
    ToDate toDate = dateMax();
    
    TransDate showAsOfDate;
    FormCheckBoxControl showAllCheckbox;
    boolean showAll;
    
    //get the selection from the parent form date effective toolbar
    showAsOfDate = effectivenessPaneController.parmShowAsOfDate();
    showAllCheckbox =  effectivenessPaneController.parmShowAllCheckbox();
    showAll = showAllCheckbox.value();
    
    if (showAll)
        this.query().validTimeStateDateRange(fromDate, toDate);
        
    if (showAsOfDate)
        this.query().validTimeStateAsOfDate(showAsOfDate);

    super();
}

Monday, December 30, 2019

Index consideration for date effective tables

it is a best practice to add ValidTo to the ValidTimeStateKey index
and on the Properties tab (of the ValidTo field in the index) to
select Yes from the Included Column.

image

[see: http://dev.goshoom.net/en/2012/04/included-columns-in-ax2012]

but if it is a clustered index see this:

Performance considerations when designing valid time state tables
To help improve performance of valid time state tables, you should index them correctly.
Valid time state tables are modeled with an alternate key that includes the ValidFrom column.
In some models, the ValidTo column may have also be included in the alternate key,
but this is not necessary for uniqueness, and it should be removed from the alternate key constraint.
If the ValidFrom column is a key column of the clustered index,
the ValidTo column should not also be a key column of the clustered index
.
If the ValidFrom column is a key column of a non-clustered index,
the ValidTo column should be made an included column in the non-clustered index,
which provides coverage for range queries that involve both ValidTo and ValidFrom columns.

for examples see where the index is a clustered index see:
\Data Dictionary\Tables\LogisticsPostalAddress

image

or see
\Data Dictionary\Tables\HcmPositionWorkerAssignment

image

Friday, December 6, 2019

SysExtension framework for Dynamics Ax example

The benefits of using this new extension model are that the base and derived classes are decoupled, and it takes less code to extend the capability of the Microsoft Dynamics AX application.

The getClassFromSysAttribute method works by searching through the classes that are derived from the our base class (EF_ExtFrameworkSample6) until it finds a class that has matching attribute

The input value of the attribute class can be anything, an enum, a string, an integer. For this example I used the class name as input.

1) Create the attribute class

As you see I added in the attribute class also a generic static method that can be used by any extension that might use this attribute

class EF_ClassNameAttribute extends SysAttribute
{
    ClassName className;
}
public void new(ClassName _className)
{
    super();
    className = _className;
}
public ClassName parmClassName(ClassName _className = className)
{
    className = _className;

    return className;
}
//this method should be in the factory class, not here.
//I took the liberty to put it here becuse I might want to share it with multiple factory classes.
public static Object getClassFromSysAttribute(ClassName _baseClassName, ClassName _className)
{
    EF_ClassNameAttribute attr;
    Object cl;

    attr = new EF_ClassNameAttribute(_className);
    cl = SysExtensionAppClassFactory::getClassFromSysAttribute(_baseClassName, attr);

    if (!cl)
    {
        throw error(Error::wrongUseOfFunction(_baseClassName));
    }

    return cl;
}



2) Create the base class

the base class does not need to be abstract, but is a good practice

abstract class EF_ExtFrameworkSample6
{
    Name name;
    MethodName functionName;
}
abstract protected void init()
{
}
public void run()
{
    this.init();
    info(strFmt('Hello my name is %1', name));
    info(strFmt('the class that run is %1', functionName));
}


3) Create the Extensions


[EF_ClassNameAttribute(classStr(EF_ExtFrameworkSample6_1))]
class EF_ExtFrameworkSample6_1 extends EF_ExtFrameworkSample6
{
}
protected void init()
{
    name = 'Pippo';
    functionName = funcName();
}
[EF_ClassNameAttribute(classStr(EF_ExtFrameworkSample6_2))]
class EF_ExtFrameworkSample6_2 extends EF_ExtFrameworkSample6
{
}
protected void init()
{
    name = 'Topolino';
    functionName = funcName();
}


4) Create the Factory class


class EF_ExtFrameworkSample6Factory
{
}
protected void new()
{
}
public static EF_ExtFrameworkSample6 newFromClassName(ClassName _className)
{
    return EF_ClassNameAttribute::getClassFromSysAttribute(classStr(EF_ExtFrameworkSample6), _className);
}
public static EF_ExtFrameworkSample6 newFromSample6_1()
{
    return EF_ExtFrameworkSample6Factory::newFromClassName(classStr(EF_ExtFrameworkSample6_1));
}
public static EF_ExtFrameworkSample6 newFromSample6_2()
{
    return EF_ExtFrameworkSample6Factory::newFromClassName(classStr(EF_ExtFrameworkSample6_2));
}
//just for trials...
public static void main(Args _args)
{
    EF_ExtFrameworkSample6 cl;
    cl = EF_ExtFrameworkSample6Factory::newFromSample6_1();
    cl.run();
}

image

One side note, The extension framework cache so every time you do some changes during development clear the cache :

static void JobEF_SysExtensionCache(Args _args)
{
    SysExtensionCache::clearAllScopes();
}