Showing posts with label map. Show all posts
Showing posts with label map. Show all posts

Monday, July 7, 2025

Packing Sets, Maps, Lists, etc. in D365FO: Why con2str can break your code

While working on what seemed like a routine task in Dynamics 365 Finance and Operations, I encountered an unexpected issue Specified cast is not valid when passing a Set as a parameter to a method. 


Here's what happened.

I had a simple and logical flow:

1. Pack a set or a list into a container with standard pack method.



2. Convert this container into a string with con2str global function.


3. Pass it to my function as an argument. (not depicted)

4. Convert this string back to a container via str2con.



5. Create a new set from the restored container via set create method.

Everything worked fine—until step 5.

Instead of a smooth reconstruction of the Set, I ran into an error. The root cause? The str2con() function. By default, str2con() interprets numeric values as Int64 (long integers). This silent type coercion corrupted the data structure expected by Set.create(), which led to the failure.


To avoid this issue altogether, do not use str2con() to restore containers that will be used for object reconstruction (like sets, maps, etc.). Instead, use:

  • con2base64str() to serialize the container

  • base64str2con() to safely deserialize it

These Global functions preserve the data types and internal structure of the container without any implicit type conversions.

Saturday, December 11, 2021

Supporting functions to work with multiple selection of Enum values

There a couple of custom static methods you can use to facilitate your job with Enum values in tables and forms.

class wzhTest
{
    public const str contSeparator = ';';
    /// <summary>
    /// Populates a map for all enum's values
    /// </summary>
    /// <param name = "_enumName">Enum name</param>
    /// <returns>Map object</returns>
    public static Map createMapForEnum(EnumName _enumName)
    {
        Map             map          = new Map(Types::Enum, Types::String);
        DictEnum        dictEnum = new DictEnum(enumName2Id(_enumName));
        for(int i = 0; i < dictEnum.values(); i++)
        {
            map.insert(dictEnum.index2Value(i), dictEnum.index2Symbol(i));
        }
        return map;
    }
    /// <summary>
    /// Creates a container with enum values from a given string values container
    /// </summary>
    /// <param name = "_cont">string values container</param>
    /// <param name = "_enumType">enum variable for defining its type</param>
    /// <returns>Container with enum value</returns>
    public static container enumValuesCont2EnumStrCont(container _cont, int _enumId)
    {
        container       ret;
        str             s;
        int             idx = 0;
        int             len = conLen(_cont);
        while (idx < len)
        {
            idx += 1;
            s           = conPeek(_cont, idx);
            if(s)
            {
                ret += enum2Symbol(_enumId, conPeek(_cont, idx));
            }
        }
        return ret;
    }

    /// <summary>
    /// Creates a string with enum values string
    /// </summary>
    /// <param name = "_s">string values separated with ; sign</param>
    /// <param name = "_enumType">enum variable for defining its type</param>
    /// <returns>String with enum values separated with ; sign</returns>
    public static str enumValuesStr2EnumStrStr(str _s, int _enumId)
    {
        container c = str2con(_s, wzhTest::contSeparator);
        c = wzhTest::enumValuesCont2EnumStrCont(c, _enumId);
        return con2Str(c, wzhTest::ContSeparator);
    }
}

Let's see how they work with AccessControlledType enum type.

 static public void main(Args _args)
    {
        AccessControlledType enumEDT;
        //AccessControlledType::MenuItemDisplay / 0
        //AccessControlledType::MenuItemOutput / 1
        //AccessControlledType::MenuItemAction /2
        //AccessControlledType::WebUrlItem /3
        //AccessControlledType::WebActionItem /4
        //AccessControlledType::WebManagedContentItem / 5
        //AccessControlledType::Table / 6
        // AccessControlledType::TableField / 7
        EnumId      id      = enumName2Id(enumStr(AccessControlledType));
        str         name    = enumId2Name(id); //enumStr(AccessControlledType)

Basically, they convert a enum values list from string and vice-versa by using standard functions.

Check the output of each static method.

        //createMapForEnum
        Info("Test createMapForEnum");
        Info(strFmt("%1 : %2 values", id, name));
        Map         m = wzhTest::createMapForEnum(name);
        MapIterator mi = new MapIterator(m);
        int i;
        while(mi.more())
        {
            Info(strFmt("%1 : '%2'", i, mi.value()));
            mi.next();
            i++;
        }


        //enumValuesCont2EnumStrCont
        Info("Test enumValuesCont2EnumStrCont");
        setPrefix('');
        container   c1 = [AccessControlledType::WebActionItem, AccessControlledType::MenuItemOutput];
        container   c2 = wzhTest::enumValuesCont2EnumStrCont(c1, id);

        for(i=1;i<=conLen(c2);i++)
        {
            Info(strFmt("%1 => '%2'", conPeek(c1,i), conPeek(c2,i)));
        }


        //enumValuesStr2EnumStrStr
        Info("Test enumValuesStr2EnumStrStr");
        str         s1 = con2Str([AccessControlledType::Table, AccessControlledType::TableField], wzhTest::contSeparator);
        str         s2 = wzhTest::enumValuesStr2EnumStrStr(s1, id);
        Info(strFmt("'%1' => '%2'", s1, s2));


Monday, April 12, 2021

Switch in View Computed column

 If you need to compare multiple fields in your view while populating a computed column, take into consideration that the standard implementation of method SysComputedColumn::switch uses a map enumerator. 

public static client server str switch(str _controlExpression, Map _comparisonExpressionMap, str _defaultExpression)
    {
        MapEnumerator  mapEnumerator;
        str caseExpression = '';

        caseExpression ='CASE ' + _controlExpression;
        mapEnumerator = _comparisonExpressionMap.getEnumerator();
        while (mapEnumerator.moveNext())
        {
            caseExpression += ' WHEN ' + mapEnumerator.currentKey() + ' THEN ' + mapEnumerator.currentValue();
        }
        caseExpression += ' ELSE ' + _defaultExpression;
        caseExpression += ' END';

        return caseExpression;
    }

It means that your given order will be replaced in final SQL clause by alphabetical one of your keys.

CASE  
	WHEN (T4.PROJID1) != ('') THEN T4.PROJNAME1 
	WHEN (T4.PROJID2) != ('') THEN T4.PROJNAME2 
	WHEN (T4.PROJID3) != ('') THEN T4.PROJNAME3 
	ELSE T4.PROJNAME END

For example, we want to populate field mgcProjInvoiceGLTransView.ParentProjectName for a given project id up to three level up in the project hierarchy. Let's assume that they are prepopulated in mgcProjInvoiceOnAccView view: projId1 is the parent of projId, projId2 is the parent of projId1, and so on.

Once we reference this computed column to the following method, we will get a new order in CASE construction on the SQL side.

 private static str projNameParent()
    {
        str         ret;
        SysComputedColumnBase::switch cannot keep a given order while using Map enumerator; so we put the SQL string as a string constant
        tableName   viewName                    = identifierStr(mgcProjInvoiceGLTransView);
        tableName   tableName                   = identifierStr(mgcProjInvoiceOnAccView);
        str         compareValue                = '';


        Map         comparisonExpressionMap     = SysComputedColumn::comparisionExpressionMap();
        str         fieldNameProjName3          = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName3));
        str         fieldNameProjName2          = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName2));
        str         fieldNameProjName1          = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName1));
        str         fieldNameProjName           = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName));

        comparisonExpressionMap.insert(
        SysComputedColumn::notEqualExpression(
            SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId3)),
            SysComputedColumn::comparisonLiteral(compareValue)),
        fieldNameProjName3);

        comparisonExpressionMap.insert(
        SysComputedColumn::notEqualExpression(
            SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId2)),
            SysComputedColumn::comparisonLiteral(compareValue)),
        fieldNameProjName2);

        comparisonExpressionMap.insert(
        SysComputedColumn::notEqualExpression(
            SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId1)),
            SysComputedColumn::comparisonLiteral(compareValue)),
        fieldNameProjName1);

        ret       = SysComputedColumn::switch(
                                                '',
                                                comparisonExpressionMap,
                                                fieldNameProjName);
        return ret;
    }

Of course, it always returns  the name of the parent on the first level, even though it had its own parent, which is incorrect result. 

Therefore, the easiest way is to replace the aforementioned construction with a simple string.

 private static str projNameParent()
    {
        str         ret;
        ret = "CASE WHEN (T4.PROJID3) != ('') THEN T4.PROJNAME3 WHEN (T4.PROJID2) != ('') THEN T4.PROJNAME2 WHEN (T4.PROJID1) != ('') THEN T4.PROJNAME1 ELSE T4.PROJNAME END";
        return ret;
    }

Monday, April 10, 2017

AIF Many to One External Codes Value Mapping and Reverse View Extension

I do not see any reason why we are not allowed to map many external codes to one internal for AIF inbound port value mapping.

In fact, this is just a question of one additional table, which can be easily created as a copy of the exting one, and a slight change to three classes and AIF related forms.


For the demo's sake it is implemented for Customer and Units only, but you can add the same to any AX externally enabled table.

 Please download and use this extension to the standard AIF in AX 2012.

Another valuable feature of this project is the External codes Reverse View.


Any time I saw something like depicted, I dreamt to have a way to look into this halo in reverse.



The Reverse view enables you to find any existing relation between 1:1 and N:1 external and internal codes.

Filter by any column, export them to Excel, and go directly to the internal table by Edit or double-clicking.

Besides aforementioned, there are examples of using the powerfull AX objects, like:
- table map;
- Data Dictionary operations for scalability;
- set;
- InMemory and TempDB usage in Form and joins.