Dynamics AX Tutorial - X++ Containers Functionality (AX_2012_R3)


Why Containers?

  • In X++ (Object oriented programming language) the data or values stored in a Variable are of below types:
    • Primitive dataTypes - int, str, real .... etc.
    • Composite dataTypes - Arrays, Containers, Collection Classes
    • Temporary Tables
  • For instance,
    • Primitive dataType variable can hold only 1 value and that too of same dataType, if you want to store 500 values then you have to create/declare 500 variables and intialize them, also it doesn't support variables of different dataTypes. To overcome this problem AX provides composite dataTypes arrays and containers, however arrays solve the former but supports only one dataType.
    • Container solves this problem by storing many elements of different dataypes. However there are few limitations with Containers like "container cannot store objects" and "performance implications" and they can be addressed by collection classes and temporary tables concepts. Containers are most frequently used in AX development so let us explore Containers and its features.

Features of Containers:

  • A container is a "composite data type"
  • A container is 1 based not 0 based.
  • A container is not a class therefore classes/objects cannot be passed/put into a container.
  • A container contains an ordered sequence of values (primitive dataTypes - int, str, real, date, boolean, enum etc.) or other containers or/and some composite data types.
  • A container is a "PASS BY VALUE" type not "pass by reference" type which means while passing container the copy of container with values is passed not the reference.
    • Therefore, any variable declared as a container is automatically initialized to an empty container that contains no values.
  • A container is IMMUTABLE which means a container is never modified with any functions rather all X++ statements acting on a container are internally building a new container.
    • For example assignment of a container to another container variable and container functions such as conIns(), conDel(), conPoke(), += etc. are actually creating a new copy of the container.
  • A container can be stored in the database as a dataBase coloumn created through AOT especially used to store images/files as a blob.

Container Functions:

  • Below are some of the most common container functions with examples.

1. conPeek() Function

  • The conPeek() is used to retrieve a specific element from a container.
  • Syntax: anytype conPeek(container container, int number)
    • container - The container to return an element from.
    • number - The position of the element to return. Specify 1 to get the first element.
  • Return value: The element in the container at the position specified by the number parameter. The conPeek function automatically converts the peeked item into the expected return type.
EXAMPLE:
void daxErp_conPeek()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // conPeek() function returns the value being held in a specific position in the container. 
    // (Note: It returns anyType)
    // Here printing 1st and 2nd values from daxConBeta to info
    charHolder = conPeek(daxConBeta, 1);
    info(strFmt("Container daxConBeta conPeek() values are :- 
    %1, %2", charHolder, conPeek(daxConBeta, 2)));
}

2. conPoke() Function 

  • The conPoke() is used to modify a container by replacing one or more of the existing elements.
  • Syntax: container conPoke(container container, int start, anytype element, ...)
    • container - The container to modify.
    • start - The position of the first element to replace.
    • element - One or more elements to replace, separated by commas.
  • Return value: The new container with the inserted elements.
Example:
void daxErp_conPoke()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // conPoke() function usage 
    // (Replaces the value being held in a specific position in the container, with a new value)
    // Note: Here Replacing 2nd value (8594) with (29) in daxConBeta
    daxConBeta  = conPoke(daxConBeta, 2, 29);
    info(strFmt("Container daxConBeta conPoke() values are :- 
    %1, %2", conPeek(daxConBeta, 1), conPeek(daxConBeta, 2)));
}

3. conIns() Function

  • The conIns() is used to insert one or more elements into a container.
  • Syntax: container conIns(container container, int start, anytype element, ...)
    • container - The container into which to insert elements.
    • start - The position at which to insert elements.
    • element - One or more elements to insert, separated by commas.
  • Return value: The new container with the inserted elements.
Example:
void daxErp_conIns()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // conIns() function usage 
    // (Inserts a value into a specific position in the container, index shifted next)
    // Note: Here Inserting value 299 at 2nd location and shifting the rest to the right!
    daxConBeta = conIns(daxConBeta, 2, 299);
    info(strFmt("Container daxConBeta conIns() values are :- %1 , %2, %3", 
    conPeek(daxConBeta, 1), conPeek(daxConBeta, 2), conPeek(daxConBeta, 3)));
}

4. Container insert operator +=

  • The += insert operator is used to insert one or more elements into a container.
  • += is faster than the conIns() function, however note that it inserts at the end after last index location. Therefore use this operation when insertion has no relevance to index position especially used in loops.
Example:
void daxErp_conInsOperator()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // += is faster than the conIns() function
    // however note it inserts at the end after last index location
    // Therefore use this operation when insertion has no relevance to index position
    daxConBeta += 2999;
    info(strFmt("Container daxConBeta += usage values are :- %1 , %2, %3, %4",
    conPeek(daxConBeta, 1), conPeek(daxConBeta, 2), conPeek(daxConBeta, 3), conPeek(daxConBeta, 4)));
}

5. conFind() Function

  • The conFind() is used to find the first occurrence of an element or a sequence of elements in a container.
  • Syntax: int conFind (container container, anytype element,... )
    • container - The container to search.
    • element - One or more elements to search for, separated by commas.
  • Return value: Returns 0 if the item was not found; otherwise, the sequence number of the item.
Example:
void daxErp_conFind()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // conFind() function finds position in the container that a certain value is being held (if found)
    info(strFmt("conFind() - Find '8594' value in daxConBeta container return if true(indexNo) else (0)
    :- %1", conFind(daxConBeta, 8594)));
    info(strFmt("conFind() - Find '2' value in daxConBeta container return if true(indexNo) else (0)
    :- %1", conFind(daxConBeta, 2)));
}

6. conLen() Function

  • The conLen() is used to retrieve the number of elements in a container.
  • Syntax: int conLen(container container)
    • container - The container in which to count the number of elements.
  • Return value: The number of elements in the container.
Example:
void daxErp_conLen()
{
    // container declaration and initialization with 2 values
    container       daxConBeta  =  ["Welcome", 8594]; 
    str             charHolder;
    ;

    // conLen() function returns the number of elements in the container
    // Here at this instance we have 2 elements []
    info(strFmt("daxConBeta conLen() container length is :- %1", conLen(daxConBeta)));
}

7. Container Retrieval Example

  • Below example shows the typical common scenario where the developer has to read the values from a container in a loop and perform other operations.
Example:
void daxErp_conLoopRetrieval()
{
    // container declaration and initialization with 2 values
    container       daxConBeta  =  ["Welcome", 8594]; 
    str             charHolder;
    int             conLength, cnt;
    ;

    // looping through container values and printing them to info
    // most common usage to retrieve values from passed container
    conLength = conLen(daxConBeta);
    info(strFmt("Container daxConBeta length/noOfValues = %1 and the values are below", conLength));
    for (cnt = 1; cnt <= conLength; cnt++)
    {
        charHolder = conPeek(daxConBeta, cnt);
        info(strFmt("Container daxConBeta %1 value = %2", cnt, charHolder));
    }
}

8. conDel() Function

  • The conDel() is used to remove the specified number of elements from a container.
  • Syntax: container conDel(container container, int start, int number)
    • container - The container from which to remove elements.
    • start - The one-based position at which to start removing elements.
    • number - The number of elements to delete.
  • Return value: A new container without the removed elements.
Example:
void daxErp_conDel()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    ;

    // conDel() function removes a value from a specific position in the container.
    // Here removing 1 value starting from 2nd location
    daxConBeta = conDel(daxConBeta, 2, 1);
    info(strFmt("Container daxConBeta conDel() values are :- 
    %1, %2", conPeek(daxConBeta, 1), conPeek(daxConBeta, 2)));
}

9. conNull() Function

  • The conNull() is used to retrieve an empty container. Use this function to explicitly dispose of the contents of a container.
  • Syntax: container conNull()
  • Return value: An empty container.
Example:
void daxErp_conNull()
{
    container daxConAlpha; // container declaration
    str charHolder;
    ;
    // container initialization with 4 values
    daxConAlpha = ["Finance", "T&L", "Retail", "Manufacturing"]; 

    // conNull() resets and returns an empty container
    // Here resets 4 values ("Finance", "T&L", "Retail", "Manufacturing") in daxConAlpha to null
    info(strFmt("Container daxConAlpha conNull() values before are :- %1 , %2, %3, %4",
    conPeek(daxConAlpha, 1), conPeek(daxConAlpha, 2),
    conPeek(daxConAlpha, 3), conPeek(daxConAlpha, 4)));
    
    daxConAlpha = conNull(); // set con null
    
    info(strFmt("Container daxConAlpha conNull() values after are :- %1 , %2, %3, %4",
    conPeek(daxConAlpha, 1), conPeek(daxConAlpha, 2),
    conPeek(daxConAlpha, 3), conPeek(daxConAlpha, 4)));
}

10. con2Str() Function

  • This a Global class method which retrieves a string of the elements in the specified container. If no value for the sep parameter is specified, the comma character will be inserted between elements in the returned string. The returned string cannot have more than 1000 characters.
  • Syntax: client server public static str Con2Str(container c, [str sep])
    • c - The container to convert.
    • sep - The string to use as a separator between elements; optional.
  • Return: A string of the elements in the container.
Example:
void daxErp_con2Str()
{
    // container declaration and initialization with 2 values
    container daxConBeta = ["Welcome", 8594]; 
    str charHolder;
    int conLength, cnt;
    ;

    // con2Str() function gets values as str comma separated from the container
    info(strFmt("Container daxConBeta con2Str() values are :- %1", con2Str(daxConBeta)));
}

11. str2con() Function

  • This a Global class method which converts the string elements into a container. The returned string cannot have more than 1000 characters.
  • Syntax: client server public static container str2con(str _value, [str _sep])
    • -value - the String
    • sep - The string to use as a separator between elements; optional.
  • Return: A container.
Example:
void daxErp_str2Con()
{
    container daxConDelta; // container declaration
    str charHolder;
    int conLength, cnt;
    ;

    // str2Con() function gets values str comma separated and converts to container
    // container initialization with 2 values using str2con()
    daxConDelta = str2con("Technical,Functional", ","); 
    info(strFmt("Container daxConDelta str2con() values are :- 
    %1 and %2", conPeek(daxConDelta, 1), conPeek(daxConDelta, 2)));
}

12. conView() Function

  • This a Global class method which retrieves or displays a form with a visual representation of a container as a tree. A form with the visual representation of a container if the _lookup parameter was true; otherwise, null.
  • Syntax: client server public static FormRun conView(container containerToShow, [str _caption, boolean _lookup])
    • containerToShow - The container to show in the form.
    • caption - A caption to use on the form; optional.
    • lookup - true to return the form without displaying it; false to display the form and return null.
  • Return: A string of the elements in the container.
Example:
void daxErp_conView()
{
    container daxConDelta; // container declaration
    str charHolder;
    int conLength, cnt;
    ;

    // str2Con() function gets values str comma separated and converts to container
    // container initialization with 2 values using str2con()
    daxConDelta = str2con("Technical,Functional", ","); 

    // to view the results in tree form use conView() global function
    conView(daxConDelta);
}

13. Container as dataType to store images/files...etc.

  • A container can be stored in the database. For example container is one of the field types to add a column to a table. This can be done only through AOT. Also container can store tablebuffer and BinData as a blob inside a container.
  • Below example shows how a container can be used to read the image from the file through dialog and store in a container and then write the image to a file location. This demonstrates how images are stored in the DAX application, this example can be further extended to be used on Forms and storing to database.
hand drawing best practice
Example:
private void daxErp_conImageExample()
{
    #define.jpeg('.jpg')
    #define.openMode("R")
    Dialog                  dialog;
    DialogField             dialogFileName;
    FileIoPermission        perm;
    FilePath                filePath;
    FileName                fileExt;
    FileName                fileName;
    Filename                fileNameWithPath;
    Bindata                 daxRead, daxSave;
    container               readPic;
    str                     daxBase64;
    ;

    dialog = new Dialog("DAX ERP TRAINING Example - 
    Read, Save and Write Image to and from the container");
    dialog.addGroup("Picture file upload");
    dialogFileName = dialog.addField("fileNameOpen", "Select JPEG image file to import"); 
    dialog.run();

    if (dialog.run())
    {
        fileNameWithPath = dialogFileName.value();
        if(!fileNameWithPath)
            throw error("Filename must be filled");

        [filePath, fileName, fileExt] = fileNameSplit(fileNameWithPath);
        if (fileExt != #JPEG)
            throw error(strFmt("The extension %1 is not allowed. 
            The correct extension should be %2.", fileext, #JPEG));

        perm = new FileIoPermission(fileNameWithPath, #openMode);
        if (perm == null)
        {
            throw error("No rights to perform this action");
        }

        perm.assert();
        daxRead = new BinData();
        daxSave = new BinData();

        if (daxRead.loadFile(fileNameWithPath))
        {
            // Reading and storing image into the container
            readPic = daxRead.getData();
        }
        CodeAccessPermission::revertAssert();
        // Writing the image to a file from the container
        daxSave.setData(readPic);
        daxBase64 = daxSave.base64Encode();
        AifUtil::saveBase64ToFile(@"C:\conExample\imageOutput.jpeg", daxBase64);
    }
}

businessman hand draws solving problem diagram

Differences between Container and Tempory Tables and Collection Classes

Let us look when to use a Container, Temporary Table and Collection Classes

  • Use a container to pass values between the client and server in a single method call and when data/values are few records as values are stored and retrived in the container sequentially.
  • As I've said earlier containers have performance implications if not used appropriately and this is especially true when there is huge data manipulation in the container because for every X++ container statement new memory is allocated for creating new container object causing the performance bottlenecks.
  • Use Temporary Tables when you handle huge amount of data for faster retrival because indexes can be setup on the table and also table passing is "Pass by reference" which will give performance boost as only pointer to the object is passed unlike in container new container copy is passed causing memory issues.
  • Use Collection Classes [List, Set, Map, Struct] to store and retrieve not only simple types such as strings and integers, but also more complex types such as records and objects. Dynamics AX collection classes have been designed for storing objects. The classes are implemented in C++ to achieve the maximum performance (they are system classes - MSDN). Check out here for good overview and examples of Collection Classes Framework.

Finally, Quick Overview of Above Container Functions

Please run by comments in below image gallery to glimpse the container examples or go-through each example above!