Skip to main content

Collection classes in AX – What, When and How to use

Data inserting, traversing and retrieving can be done with many different ways in AX. We do have different collection classes for it and each one has its own importance and performance impact. Let’s talk about each one by one and compare it.

  • -          are structures, contain values of any X++ type that are accessed sequentially
  • -          all values must be of same X++ type (specified through baseEnum types)
  • -          type is defined on creation and cannot be modified after initialization
  • -          can be traversed through ListEnumerator class
  • -          values can be added from both sides e.g. using addEnd() and addStart() methods 



Example 1

static void ListExample1(Args _args)
{
    List list = new List(Types::String);    // Declare and initialize List of String type
    ListEnumerator  enumerator;             // Declare ListEnumerator class to traverse list
       
    list.addEnd("Faisal");                  // Add values at End of the List
    list.addEnd("Fareed");                  // Add values at End of the List
    list.addStart("AX Developer");          // Add values at Start of the List
   
    enumerator = list.getEnumerator();      // initialize listEnumerator class object
   
    while (enumerator.moveNext())
    {
        print enumerator.current();
    }
    pause;   
} 

 Output:

Traverse from left to right

Example 2


Lists can be initialized from container too.
static void ListExample2(Args _args)
{
    Container       companyRange;               // Declare container
    List            list;                       // Declare List
    ListEnumerator  enumerator;                 // Declare ListEnumerator class to traverse list       
   
    companyRange = [curext()];                  // initialize container with current company
       // we can pass multiple values here but need to be of same  
          datatype as this is used in List in below code       
    list = con2List(companyRange);
   
    enumerator = list.getEnumerator();          // initialize listEnumerator class object
   
    while (enumerator.moveNext())
    {
        print enumerator.current();
    }
    pause;   
}

Example 3

Below code is actually an example to copy user roles from one user to another. Where I used ‘List’ to store records what roles have been moved across.
static void ListExample3(Args _args)
{
    boolean                 ret = true;

    SecurityRole            securityRole;

    SecurityUserRole        securityUserRole;
    SecurityUserRole        securityUserRoleExist;
    SecurityUserRole        securityUserRoleInsert;
    OMUserRoleOrganization  userRoleOrganization, userRoleOrganization_Insert;

    List                    copiedUserRoles = new List(Types::String);

    ListEnumerator          lEnumerator;   

    try
    {       
        while select securityUserRole
                where securityUserRole.User == fromUser
            notExists join * from securityUserRoleExist
                where securityUserRoleExist.SecurityRole    == securityUserRole.SecurityRole
                    && securityUserRoleExist.User           == toUser
        {
            select securityRole where securityRole.RecId == securityUserRole.SecurityRole;

            copiedUserRoles.addStart(securityRole.Name);

            securityUserRoleInsert.initValue();
            securityUserRoleInsert.SecurityRole = securityUserRole.SecurityRole;
            securityUserRoleInsert.User         = toUser;
            securityUserRoleInsert.insert();
            securityUserRoleInsert.clear();

            while select userRoleOrganization
                    where userRoleOrganization.User == fromUser
                        && userRoleOrganization.SecurityRole == securityUserRole.SecurityRole
            {
                userRoleOrganization_Insert.initValue();

                userRoleOrganization_Insert.OMHierarchyType             = userRoleOrganization.OMHierarchyType;
                userRoleOrganization_Insert.OMInternalOrganization      = userRoleOrganization.OMInternalOrganization;
                userRoleOrganization_Insert.SecurityRole                = userRoleOrganization.SecurityRole;
                userRoleOrganization_Insert.SecurityRoleAssignmentRule  = userRoleOrganization.SecurityRoleAssignmentRule;
                userRoleOrganization_Insert.User                        = toUser;

                userRoleOrganization_Insert.insert();
                userRoleOrganization_Insert.clear();
            }
        }
    }
    catch
    {
        ret = false;
    }

    if (ret)
    {
        lEnumerator = copiedUserRoles.getEnumerator();

        if (copiedUserRoles.empty())
            info(strFmt("User %1 and %2 already have the same user role",fromUser, toUser));

        while (lEnumerator.moveNext())
        {
            info(strFmt('%1',lEnumerator.current()));
        }
    }
    else
        error(strFmt("Aborted please review error list above"));
}

You can also have a look on this post to find difference between Iterators and Enumerators.                     

  • -          can be used as a temp data store for the given scope of a process
  • -          are much quicker than temp tables
  • -          allows you to associate one value (the key) with another value
  • -          values can be any valid X++ type

static void mapExample1(Args _args)
{
    Map             mapExample;
    MapEnumerator   mapEnumeratorExample;
    CustTable       custTable;
   
    mapExample = new Map(Types::String,Types::String);
   
    select custTable where custTable.AccountNum == "144212";
   
    mapExample.insert(CustTable.AccountNum, CustTable.name());
   
    mapEnumeratorExample = new MapEnumerator(mapExample);
   
    while (mapEnumeratorExample.moveNext())
    {
        if (!mapExample.empty())
        {           
            print mapExample.lookup(custTable.AccountNum);
        }       
    }
    pause;
}

Good information about Maps is also shared here



Containers can be used to get multiple values returned from any method.

Original method
private container getActualAmount()
{
    Container   amountContainer;
    container   textContainer;   
    ;

    for()
    {       
        amountContainer = conIns(amountContainer);
        textContainer   = conIns(textContainer);       
    }

    return [amountContainer, textContainer];
}

Called From
container   AmountsC = conNull();
container   TextC    = conNull();

[AmountsC, TextC]   = this.getActualAmount();

Set values to use for your logic

ExampleTable.Field1 = conPeek(AmountsC,1);

ExampleTable.Field2 = conPeek(AmountsC,2);

Important points about Containers

  • They are value based structure. So no class based object stored on it.
  • They are similar to linked list or array like structured.
  • Their size fixed at time of declaration. When you insert update or delete any value from container, a new container is created.
  • They always copied by value.
  • Containers are base 1 not like array based zero.

Comments

Popular posts from this blog

The Dual Write implementation - Part 1 - Understand and Setup

What is Dual-write? Tightly couples – complete at one transaction level Near real time Bi-directional Master data and business documents – Customer records you are creating and modifying and at this document we are talking about sales orders or quotes and invoice. Master data could be reference data e.g. customer groups and tax information Why Dual-write and why not Data Integrator? Data Integrator is Manual or Scheduled One directional Now, Let's deep dive and understand what is required for Dual-write setup and from where to start. First thing first, check you have access to https://make.powerapps.com/ Choose right environment of CDS (CE) Make sure you have access to the environment too, click on gear icon and Admin Center  Look for required environment and Open it, you must have access as going forward you are going to configure dual write steps in the environment user the same user you are logged in now. Now, go back to power platform admin center and...

D365FO: Entity cannot be deleted while dependent Entities for a processing group exist. Delete dependent Entities for a processing group and try again.

Scenario: There are times when you want to delete an entity from target entity list and when you do so, you face an error message which does not tell you where exactly the entity has been used.  "Entity cannot be deleted while dependent Entities for the processing group exist. Delete dependent Entities for a processing group and try again. " Solution: Browse the environment by appending this part  /?mi=SysTableBrowser&TableName=DMFDefinitionGroupEntity&cmp=USMF   at the end.  For example; if the environment URL is  https://daxture.sandbox.operations.dynamics.com then the complete URL will be https://daxture.sandbox.operations.dynamics.com/?mi=SysTableBrowser&TableName=DMFDefinitionGroupEntity&cmp=USMF Filter for Entity and it will give you the DefinitionGroup where the entity has been added or used in data management import/export projects. Get the DefinitionGroup name and search in the export/import projects under data management and either del...

Dual-write connection set error: An item with the same key has already been added

If you happen to see this error message then you have duplicate records in cdm_company entity in CDS environment. Check for cdm_companycode field this is normally not allowed but have a look and delete the ones with duplicates.