Inside Over layering Vs Extension

The Layer based approach has been used in Dynamics AX application.
You will be able to customize the SYS layer object in VAR, USR, CUS layers.
For an example, When a developer customise the AOT object in VAR layer, AX system makes copy the of the object from SYS layer to VAR layer. If the same object has their copies in different layers, the application uses top layer during run time.

Now we are going to see, How D365O system internally works when a developer overlay the source code/Object.

Lets take example of customizing the AssetCondition table.

Asset condition table standard table which is included in Application suite packages. If you want to customise AssetCondition table using overlayering, you need to create new model in ApplicationSuite package. When you do build on AssetCondition  table object, Application suite package dll will gets updated.

There are two options available for model creation.

1. Creating new model in existing package
2. Create new model in new package

Over layering should be done in existing package i.e New model should be created in application suite package and customisation should be done on that. In our example, AssetCondition Table should be customised in “Your Model” of the Application suite package.

You cannot customise the object outside of their own package. As per our example, You cannot customise the AssetCondition table in other packages such as Currency, Directory etc…

The new package option will be used only for extension.

Lets take a look at standard Asset condition table in AOT design and XML format.

AOT Design:-

3

XML Format:-

The xml file is located at C:\AOSService\PackagesLocalDirectory\ApplicationSuite\Foundation\AxTable
Foundation is standard model created by Microsoft.

2

 

 

 

 

 

 

 

 

 

 

Now Let us add new string type field “Test” to AssetCodition table using over layering. You need to open Application explorer and click on Customise option by right click on the Asset condition table.

4

 

When you click on customise option, system creates a new file in delta folder, that means, Object is ready for customization.  What ever changes you going to do, will be updated in delta file. The delta file will be located at C:\AOSService\PackagesLocalDirectory\ApplicationSuite\YOURMODEL\AxTable\Delta

System does not update anything on the base file which is located at C:\AOSService\PackagesLocalDirectory\ApplicationSuite\Foundation\AxTable

Delta file schema:-

5

When application compile, the kernel program internally compile these two source files and update the ApplicationSuite dll.

New field has been added to Asset condition table.

1

Delta file schema after adding new Field:-

6

There are few nodes will be created under “Fields” node. If you do any customisation in methods, below nodes will be created under methods node.

  1. PathElement
  2. Added
  3. Changed
  4. Deleted
  5. OriginalOrder
  6. YoursOrder

PathElement : – Element in which the customization is added. If you look at above image, field is path. Because we have added new field under field node.

Added: The newly added meta data details will be updated in this node. The newly added field ‘Test” and their details will be updated under added node.

7

Changed:- If you have updated anything on top of existing metadata (In our case – Fields) will be updated in changed node. For example, I have updated Label in “Condition” field(Standard field in Asset condition table). The condition field does not have any label before. I have updated new label as “Test for changed”. So the <Original> node is blank and <Yours> is updated as “Test for changed”(Highlighted in yellow).

8

Deleted:- I haven’t idea on this. 

Original order:- Original order is “Before changing”. As per our example, This node contains the details of fields before adding new field.

9

Your order: – Your order is “After changing”. As per our example, This node contains the details of fields after adding new field.

10

 

Conflicts:- In case of any conflicts in that object, that will be added to conflicts node.

So all over layering changes has been updated in delta files in own package and updated in same dll file. But for extension, we should have separate package.

Extension:-

When the same field “Test” is added using extension, new file will be created in “Your model” of the new package. Lets say my new package is “ExtensionTest”. This new package will have separate dll file. when you compile the object, the changes will be updated to “ExtensionTest.dll” file.  The extension xml file will only having the details of new field. If you compare extension file with over layering file, extension is light weight so compilation will be faster than over layering. Because, over layering will have to compile Added, changed, OriginalOrder, YoursOrder..etc nodes. But extension will have to compile only Field nodes(Yellow highlighted).  We are not touching application suite files for adding new fields. This wouldn’t give conflict issues during upgrades.

11

Advertisements

Upgrade AX2012 RTM custom tables to D365 tables using X++ script.

I thought of doing some thing different in D365O. At that moment, I got an idea to work on Ax2012 RTM table upgrades. After spending huge amount of time in analysis, I was started writing script to upgrade AX2012 tables to D365O tables. Off course, you could have a question to me, “Why don’t you use LCS upgrade services?”. Answer is “Yes”, Always you should go for Microsoft recommended approach. But some cases we can’t avoid shortcuts.

Steps:-

  • Create folders as mentioned in below program. The xml files are stored in these files.
  • Create private project ‘TestUpgrade’ and add table group node.
  • Add custom tables to the project that to be upgraded to D365.
  • If you run the class ‘SK_MetaDataXMLCreate’, which will ask the project name.
  • Provide ‘TestUpgrade’ & click ok.
  • It will generate the xml file to the specified folded.
  • Copy and paste the xml file to D365 VM and add the upgraded tables to project.
  • Do compile and sync.

Project

Class1:-

class SK_EDTMetaDataCreate
{

}
public void createEDTMetaData(SysDictField dictField)
{
    XmlDocument             object;
    XmlElement              parentElement;
    ListEnumerator          nodeListEnumerator;

    str                     fileName;
    int                     nodeCount;
    boolean                 isNameSpace;
    boolean                 isText;
    str                     text;

    fileName    = "E:\\EDT\\" + dictField.typeName() + ".xml";

    object = XmlDocument::newBlank();

    nodeListEnumerator = this.initializeEDTNodeList().getEnumerator();

    while (nodeListEnumerator.moveNext())
    {
        if (nodeListEnumerator.current() == 'AxEdt')
        {
            isNameSpace = NoYes::Yes;
            parentElement = this.createNodes(nodeListEnumerator.current(),
                                        object,
                                        parentElement,
                                        isNameSpace,
                                        isText,
                                        text,
                                        dictField);
        }

        else
        {

            this.createNodes(nodeListEnumerator.current(),
                                            object,
                                            parentElement,
                                            isNameSpace,
                                            isText,
                                            text,
                                            dictField);
        }

        isNameSpace = false;
        isText      = false;
        nodeCount++;

    }

    object.save(fileName);
}

public XmlElement createNodes(Name          name,
                            XmlDocument     object,
                            XmlElement      parentElement,
                            boolean         isNameSpace = false,
                            boolean         isText = false,
                            Name            text = '',
                            SysDictField    dictField = null)
{
    XmlElement  childElement;
    str         fieldAttribute;

    childElement = object.createElement(name);

    if (isText)
    {
        childElement.appendChild(object.createTextNode(text));
    }

    if (isNameSpace)
    {
        if (dictField.baseType() != Types::Integer)
        {
            fieldAttribute = 'AxEdt'+ enum2str(dictField.baseType());
        }
        else
        {
            fieldAttribute = 'AxEdtInt';
        }
        object.appendChild(childElement);
        //childElement.setAttribute("xmlns:i","");
        childElement.setAttribute("xmlns:i","http://www.w3.org/2001/XMLSchema-instance");
        childElement.setAttribute("xmlns","");
        childElement.setAttribute2("type", "http://www.w3.org/2001/XMLSchema-instance", fieldAttribute);

    }
    else
    {
        switch (name)
        {
            case 'Name':
                childElement.appendChild(object.createTextNode(dictField.typeName()));
                break;
        }
        parentElement.appendChild(childElement);
    }

    return childElement;
}

//EDT, Enum will be added later
public List initializeEDTNodeList()
{
    List nodeFieldList = new List(Types::String);

    nodeFieldList.addEnd('AxEdt');
    nodeFieldList.addEnd('Name');
    nodeFieldList.addEnd('ArrayElements');
    nodeFieldList.addEnd('Relations');
    nodeFieldList.addEnd('TableReferences');

    return nodeFieldList;
}

Class2:-

class SK_MetaDataXMLCreate extends RunBase
{
    DialogField             projectNameDF;
    ProjName                projectName;

    XmlDocument             doc;
    XmlElement              root;
    XmlElement              nodeName;
    XmlElement              nodeLabel;
    XmlElement              nodeValue;
    XmlElement              nodeEnumvalues;
    XmlElement              nodeAxEnumvalue;
    TreeNode                enumTreeNode;
    TreeNodeIterator        iterator;
    Filename                fileName, sourceFile;
    Counter                 counter, counterEnumObjects;

    XmlWriterSettings       writerSettings;
    XmlTextWriter           writer;

        //

    DictTable dictTable;
    //
    #TreeNodeSysNodeType
}

public void createDeleteActionMetaData(XmlDocument object, XmlElement  deleteActionsElement)
{
    XmlElement          deleteActionElement;
    ListEnumerator      nodeListEnumerator;
    Str                 attribute;
    int                 deleteActionCount;

    for (deleteActionCount = 1; deleteActionCount <= dictTable.deleteActionCnt(); deleteActionCount++)
    {
        deleteActionElement = object.createElement('AxTableDeleteAction');
        nodeListEnumerator = this.initializeDeleteActionsNodeList().getEnumerator();
        while (nodeListEnumerator.moveNext())
        {

            switch (nodeListEnumerator.current())
            {
                case 'Name':
                    attribute = tableId2name(dictTable.deleteActionTableId(deleteActionCount));
                    break;
                case 'DeleteAction':
                    attribute = this.findDeleteAction(dictTable.deleteActionType(deleteActionCount));
                    break;
                case 'Relation':
                    attribute = dictTable.deleteActionRelation(deleteActionCount);
                    break;
                case 'Table':
                    attribute = tableId2name(dictTable.deleteActionTableId(deleteActionCount));
                    break;
            }


            this.createNodes(nodeListEnumerator.current(),
                            object,
                            deleteActionElement,
                            false,
                            true,
                            attribute);

        }

        deleteActionsElement.appendChild(deleteActionElement);
    }
}

public void createFieldGroupMetaData(XmlDocument object, XmlElement  fieldGroupsElement)
{

    container           fieldGroupList;
    XmlElement          fieldGroupElement;
    XmlElement          fieldGroupFieldsElement;
    XmlElement          fieldGroupFieldElement;
    DictFieldGroup      dictFieldGroup;
    DictField           dictField;
    str                 text;
    str                 nodeListStr;
    int                 fieldId;
    int                 fieldGroupCount;
    int                 fieldTotalCnt;
    int                 fieldCnt;




    fieldGroupList     = this.initializeFieldGroupNodeList();

    for(fieldGroupCount = 1; fieldGroupCount <= dictTable.fieldGroupCnt(); fieldGroupCount++)
    {
        if (!conFind(fieldGroupList,  dictTable.fieldGroup(fieldGroupCount)))
        {
            fieldGroupList += dictTable.fieldGroup(fieldGroupCount);
        }
    }

    for(fieldGroupCount = 1; fieldGroupCount <= conLen(fieldGroupList); fieldGroupCount++)
    {
        fieldGroupElement = object.createElement('AxTableFieldGroup');

        dictFieldGroup = new DictFieldGroup(dictTable.id(), conPeek(fieldGroupList, fieldGroupCount));

        // Field group name
        this.createNodes('Name',
                         object,
                         fieldGroupElement,
                         false,
                         true,
                         dictFieldGroup != null ? dictFieldGroup.name() : any2str(conPeek(fieldGroupList, fieldGroupCount)));

        // Field group Label
        if (dictFieldGroup && dictFieldGroup.label())
        {
            this.createNodes('Label',
                             object,
                             fieldGroupElement,
                             false,
                             true,
                             dictFieldGroup.label());
        }

        fieldGroupFieldsElement  = object.createElement('Fields');

        fieldTotalCnt = (dictFieldGroup != null) ? dictFieldGroup.numberOfFields() : 0;

        for (fieldCnt = 1; fieldCnt <= fieldTotalCnt; fieldCnt++)
        {
            fieldGroupFieldElement   = object.createElement('AxTableFieldGroupField');
            fieldId = dictFieldGroup.field(fieldCnt);
            dictField = new DictField(dictTable.id(), FieldId);

            this.createNodes('DataField',
                         object,
                         fieldGroupFieldElement,
                         false,
                         true,
                         (dictField != null) ? dictField.name(): dictFieldGroup.methodName(fieldId));
            fieldGroupFieldsElement.appendChild(fieldGroupFieldElement);
        }

        fieldGroupElement.appendChild(fieldGroupFieldsElement);
        fieldGroupsElement.appendChild(fieldGroupElement);

    }

}

public void createFieldSchema(XmlDocument object,
                              XmlElement  fieldListElement)
{
    ListEnumerator          nodeListEnumerator;
    XmlElement              fieldElement;
    SK_EDTMetaDataCreate   edtMetaData = new SK_EDTMetaDataCreate();
    int                     i;
    int                     fieldCount = dictTable.fieldCnt();
    SysDictField            dictField;
    str                     nodeListStr;
    str                     fieldAttribute;
    Str                     text;




     for (i = 1; i <= dictTable.fieldCnt(); i++)
     {
        dictField  = new SysDictField(dictTable.id(), dictTable.fieldCnt2Id(i));
        nodeListEnumerator = this.initializeFieldNodeList().getEnumerator();

        if (!dictField.isSystem())
        {
            fieldElement = object.createElement('AxTableField');
            if (dictField.baseType() != Types::Integer)
            {
                fieldAttribute = 'AxTableField'+ enum2str(dictField.baseType());
            }
            else
            {
                fieldAttribute = 'AxTableFieldInt';
            }

            fieldElement.setAttribute("xmlns","");
            fieldElement.setAttribute2("type", "http://www.w3.org/2001/XMLSchema-instance", fieldAttribute);
            while (nodeListEnumerator.moveNext())
            {
                nodeListStr = nodeListEnumerator.current();

                switch (nodeListStr)
                {
                    case 'Name':
                        text = dictField.name();
                        break;
                    case 'Label':
                        text = dictField.label() != '' ? dictField.label() : '';
                        break;
                    case 'HelpText':
                        text = dictField.help() != '' ? dictField.help() : '';
                        break;
                    case 'Mandatory':
                        text = dictField.mandatory() == true ?'Yes':'';
                        break;
                    case 'AllowEditOnCreate':
                        text = dictField.allowEditOnCreate() == false ?'No':'';
                        break;
                    case 'AllowEdit':
                        text = dictField.allowEdit() == false ?'No':'';
                        break;
                    case 'Visible':
                        text = dictField.visible() == false ?'No':'';
                        break;
                    case 'ExtendedDataType':
                        text = dictField.typeName() != '' ? dictField.typeName() : '';
                        if (dictField.typeId())
                        {
                            edtMetaData.createEDTMetaData(dictField);
                        }
                        break;
                    case 'EnumType':
                        text = enumId2Name(dictField.enumId()) != '' ? enumId2Name(dictField.enumId()) : '';
                        break;
                }
                if (text)
                {
                    this.createNodes(nodeListEnumerator.current(),
                                                    object,
                                                    fieldElement,
                                                    false,
                                                    true,
                                                    text);
                }
            }
            fieldListElement.appendChild(fieldElement);
        }

    }

}

public void createIndexMetaData(XmlDocument object, XmlElement  indexesElement)
{

    XmlElement          indexElement;
    XmlElement          indexFieldsElement;
    XmlElement          indexFieldElement;
    DictIndex           dictIndex;
    int                 indexCount;
    int                 indexFieldCnt;


    for(indexCount = 1; indexCount <= dictTable.indexCnt(); indexCount++)
    {
        dictIndex = new DictIndex(dictTable.id(), dictTable.indexCnt2Id(indexCount));
        indexElement = object.createElement('AxTableIndex');
        this.createNodes('Name',
                         object,
                         indexElement,
                         false,
                         true,
                         dictIndex.name());
        indexFieldsElement  = object.createElement('Fields');
        indexFieldElement   = object.createElement('AxTableIndexField');

        indexFieldCnt = dictIndex.numberOfFields();
        for (indexFieldCnt = 1; indexFieldCnt <= dictIndex.numberOfFields(); indexFieldCnt++)
        {

            this.createNodes('DataField',
                         object,
                         indexFieldElement,
                         false,
                         true,
                         fieldId2name(dicttable.id(), dictIndex.field(indexFieldCnt)));

        }

        indexElement.appendChild(indexFieldsElement);
        indexFieldsElement.appendChild(indexFieldElement);
        indexesElement.appendChild(indexElement);
    }
}

public void createMethodSource(XmlDocument object, XmlElement  sourceCodeElement)
{
    XmlElement          declarationElement;
    XmlCDataSection     declarationCData;
    XmlElement          methodsElement;
    XmlElement          methodElement;
    XmlElement          sourceElement;

    SysDictMethod       dictMethod;
    int                 methodCount;
    int                 methodTotalCount;
    Name                methodName;
    TreeNode            treeNode;
    str                 declaration = 'public class' + ' ' + dictTable.name() + ' ' + 'extends common { }';
    #AOT

    void createMethodNodes(boolean isStatic)
    {
        methodTotalCount = (isStatic == true) ? dictTable.staticMethodCnt() : dictTable.objectMethodCnt();
        for (methodCount = 1; methodCount <= methodTotalCount; methodCount++)         {             methodElement   = object.createElement('Method');             methodName      = (isStatic == true) ? dictTable.staticMethod(methodCount) : dictTable.objectMethod(methodCount);             this.createNodes('Name',                             object,                             methodElement,                             false,                             true,                             methodName);             treeNode = TreeNode::findNode(#TablesPath                                             + #AOTDelimiter                                             + dictTable.name()                                             + #AOTDelimiter                                             + 'Methods'                                             + #AOTDelimiter                                             + methodName);             this.processCDataSection(object,                                     methodElement,                                     treeNode);             methodsElement.appendChild(methodElement);         }     }     declarationElement = object.createElement('Declaration');     declarationCData = object.createCDataSection(declaration);     declarationElement.appendChild(declarationCData);     sourceCodeElement.appendChild(declarationElement);     methodsElement     = object.createElement('Methods');     createMethodNodes(true);     createMethodNodes(false);     sourceCodeElement.appendChild(methodsElement); } public XmlElement createNodes(Name        name,                             XmlDocument object,                             XmlElement  parentElement,                             boolean     isNameSpace = false,                             boolean     isText = false,                             Name        text = '') {     XmlElement  childElement;     childElement = object.createElement(name);     if (isText)     {         childElement.appendChild(object.createTextNode(text));     }     if (isNameSpace)     {         object.appendChild(childElement);         childElement.setAttribute('xmlns:i','http://www.w3.org/2001/XMLSchema-instance');     }     else     {         switch (name)         {             case 'Fields':                 this.createFieldSchema(object, childElement);                 break;             case 'FieldGroups':                 this.createFieldGroupMetaData(object, childElement);                 break;             case 'Indexes':                 this.createIndexMetaData(object, childElement);                 break;             case 'DeleteActions':                 this.createDeleteActionMetaData(object, childElement);                 break;             case 'SourceCode':                 this.createMethodSource(object, childElement);                 break;         }         parentElement.appendChild(childElement);     }     return childElement; } public void createTableSchema() {     XmlDocument             object;     XmlElement              parentElement;     int                     nodeCount;     boolean                 isNameSpace;     boolean                 isText;     str                     text;     ListEnumerator          nodeListEnumerator;     fileName    = "E:\\AX Config Files\\" + dictTable.name() + ".xml";     object = XmlDocument::newBlank();     nodeListEnumerator = this.initializeTableNodeList().getEnumerator();     while (nodeListEnumerator.moveNext())     {         //if (nodeCount == 0)         if (nodeListEnumerator.current() == 'AxTable')         {             isNameSpace = NoYes::Yes;             parentElement = this.createNodes(nodeListEnumerator.current(),                                         object,                                         parentElement,                                         isNameSpace,                                         isText,                                         text);         }         //if (nodeCount == 1)         if (nodeListEnumerator.current() == 'Name')         {             isText = NoYes::Yes;             text = dictTable.name();         }         if (nodeCount > 0)
        {
            this.createNodes(nodeListEnumerator.current(),
                            object,
                            parentElement,
                            isNameSpace,
                            isText,
                            text);
        }

        isNameSpace = false;
        isText      = false;
        nodeCount++;

    }

    object.save(fileName);
}

private void createXMLBaseEnum(TreeNode     _treeNode)
{
    try
    {
        sourceFile  = "C:\\SharedProjectXmlExport\\AxEnum\\SourceEnumRandomEnumXml123.xml";
        fileName    = "C:\\SharedProjectXmlExport\\AxEnum\\" + _treeNode.AOTname() + ".xml";
        winapi::copyfile(sourceFile, fileName, true);

        counter     = 0;
        doc         = XmlDocument::newFile(fileName);
        /*
        root        = doc.createElement('AxEnum');
        root.setAttribute("xmlns:i", 'http://www.w3.org/2001/XMLSchema-instance');
        doc.appendChild(root);
        */
        root = doc.root();
        root.removeChild(root.getNamedElement("Name"));

        iterator    = _treeNode.AOTiterator();
        nodeName    = doc.createElement("Name");
        nodeName.appendChild(doc.createTextNode(_treeNode.AOTgetProperty('Name')));
        root.appendChild(nodeName);

        root.removeChild(root.getNamedElement("Label"));
        nodeLabel   = doc.createElement("Label");
        nodeLabel.appendChild(doc.createTextNode(_treeNode.AOTgetProperty('Label')));
        root.appendChild(nodeLabel);

        root.removeChild(root.getNamedElement("EnumValues"));
        nodeEnumvalues  = doc.createElement("EnumValues");
        enumTreeNode = iterator.next();
        while(enumTreeNode)
        {
            nodeAxEnumvalue = doc.createElement("AxEnumValue");
            nodeName    = doc.createElement("Name");
            nodeName.appendChild(doc.createTextNode(enumTreeNode.AOTgetProperty('Name')));
            nodeAxEnumvalue.appendChild(nodeName);
            if(enumTreeNode.AOTgetProperty('Label'))
            {
                nodeLabel   = doc.createElement("Label");
                nodeLabel.appendChild(doc.createTextNode(enumTreeNode.AOTgetProperty('Label')));
                nodeAxEnumvalue.appendChild(nodeLabel);
            }
            if(counter)
            {
                nodeValue   = doc.createElement("Value");
                nodeValue.appendChild(doc.createTextNode(enumTreeNode.AOTgetProperty('EnumValue')));
                nodeAxEnumvalue.appendChild(nodeValue);
            }
            nodeEnumvalues.appendChild(nodeAxEnumvalue);
            enumTreeNode = iterator.next();
            counter++;
        }
        root.appendChild(nodeEnumvalues);
        /*
        // Configure the XML writer
        writerSettings = new XmlWriterSettings();

        //writerSettings.newLineHandling(XmlNewLineHandling::Replace);
        writerSettings.indent(true);
        */
        // Save the XML to your file
        new FileIoPermission(fileName, 'rw').assert();
        doc.save(fileName);
        info(strFmt("%1 created successfully.", fileName));
        counterEnumObjects++;
        /*
        writer = XmlWriter::newFile(fileName, writerSettings);
        doc.writeTo(writer);
        writer.flush();
        writer.close();
        */
        CodeAccessPermission::revertAssert();
        //doc.save(fileName);
    }
    catch(Exception::Error)
    {
        throw error("Error occured during xml creation for base enums.");
    }

}

protected Object Dialog()
{
    Dialog dialog = super();

    // Set a title for dialog
    dialog.caption("XML creation");

    // Add a new field to Dialog
    projectNameDF = dialog.addField( extendedTypeStr(Name), 'Shared project name');

    return dialog;
}

public str findDeleteAction(int cnt)
{
    str DeleteActionType;

    switch (cnt)
    {
        case 0:
            DeleteActionType = 'None';
            break;
        case 1:
            DeleteActionType = 'Casecade';
            break;
        case 2:
            DeleteActionType = 'Restricted';
            break;
        case 3:
            DeleteActionType = 'Casecade + Restricted';
            break;
    }

    return DeleteActionType;
}

public boolean getFromDialog()
{
    Boolean     ret = super();

    // Retrieve values from Dialog
    if(ret)
    {
        projectName = projectNameDF.value();
        if(!projectName)
        {
            ret = checkFailed("Please specify a private project name.");
        }
    }

    return ret;
}

public List initializeDeleteActionsNodeList()
{
    List nodeDeleteActionList = new List(Types::String);

    nodeDeleteActionList.addEnd('Name');
    nodeDeleteActionList.addEnd('DeleteAction');
    nodeDeleteActionList.addEnd('Relation');
    nodeDeleteActionList.addEnd('Table');

    return nodeDeleteActionList;
}

public container initializeFieldGroupNodeList()
{
    return ['AutoReport', 'AutoLookup', 'AutoIdentification', 'AutoSummary', 'AutoBrowse'];
}

//EDT, Enum will be added later
public List initializeFieldNodeList()
{
    List nodeFieldList = new List(Types::String);

    nodeFieldList.addEnd('Name');
    nodeFieldList.addEnd('AllowEdit');
    nodeFieldList.addEnd('AllowEditOnCreate');
    nodeFieldList.addEnd('ExtendedDataType');
    nodeFieldList.addEnd('HelpText');
    nodeFieldList.addEnd('Label');
    nodeFieldList.addEnd('Mandatory');
    nodeFieldList.addEnd('Visible');


    return nodeFieldList;
}

public List initializeTableNodeList()
{
    List nodeTableList = new List(Types::String);

    nodeTableList.addEnd('AxTable');
    nodeTableList.addEnd('Name');
    nodeTableList.addEnd('SourceCode');
    /*
    nodeTableList.addEnd('ConfigurationKey');
    nodeTableList.addEnd('DeveloperDocumentation');
    nodeTableList.addEnd('FormRef');
    nodeTableList.addEnd('Label');
    nodeTableList.addEnd('TableGroup');
    nodeTableList.addEnd('TitleField1');
    nodeTableList.addEnd('TitleField2');
    nodeTableList.addEnd('CacheLookup');
    nodeTableList.addEnd('ClusteredIndex');
    nodeTableList.addEnd('CreatedRecIdIndex');
    nodeTableList.addEnd('PrimaryIndex');
    nodeTableList.addEnd('TableContents');
    */
    nodeTableList.addEnd('DeleteActions');
    nodeTableList.addEnd('FieldGroups');
    nodeTableList.addEnd('Fields');
    nodeTableList.addEnd('FullTextIndexes');
    nodeTableList.addEnd('Indexes');
    nodeTableList.addEnd('Mappings');
    nodeTableList.addEnd('Relations');
    nodeTableList.addEnd('StateMachines');

    return nodeTableList;
}

public void InitializeTableObject(TreeNode treeNode)
{
    Name            tablename = treeNode.AOTname();
    dictTable       = new SysDictTable(tableName2Id(tablename));

    this.createTableSchema();
}

public XmlElement processCDataSection(XmlDocument object,
                                XmlElement  parentElement,
                                TreeNode    treeNode)
{
    XmlCDataSection     declarationCData;
    XmlElement          sourceElement;

    sourceElement = object.createElement('Source');
    declarationCData    = object.createCDataSection(treeNode.AOTgetSource());
    sourceElement.appendChild(declarationCData);
    parentElement.appendChild(sourceElement);
    return sourceElement;
}

public void run()
{
    ProjectListNode     list = infolog.projectRootNode().AOTfindChild("Private");
    TreeNodeIterator    tIterator = list.AOTiterator();
    ProjectNode         projNodeInspect;
    ProjectNode         projNode = list.AOTfindChild(projectName);

    counterEnumObjects = 0;

    if(projNode)
    {
        info(strFmt("******** Project : %1 ********", projectName));
        projNodeInspect = projNode.loadForInspection();
        this.searchAllObjects(projNodeInspect);
        projNodeInspect.treeNodeRelease();
    }
    else
    {
        info(strFmt("Shared project : %1 not found.", projectName));
    }

    info(strFmt("%1 base enum xmls created", counterEnumObjects));
}

Public void searchAllObjects(projectNode rootNode)
{
    TreeNode            childNode;
    TreeNodeIterator    rootNodeIterator;
    int i;
    ;
    if (rootNode)
    {
        rootNodeIterator = rootNode.AOTiterator();
        childNode = rootNodeIterator.next();
        if(childNode)
        {
            while (childnode)
            {
                if (childNode.TreeNodeType().id() == #NT_PROJECT_GROUP)
                {
                    this.searchAllObjects(childNode);
                }
                else
                {
                    switch(childNode.TreeNodeType().id())
                    {
                        case #NT_DBTABLE:
                            this.InitializeTableObject(childNode);
                            break;
                        default:
                            break;
                    }
                }
                childNode = rootNodeIterator.next();
            }
        }
        else
        {

            info(strFmt("No %1 found.", rootNode.AOTgetProperty('ProjectGroupType')));
        }
    }
}

public static void main(Args _args)
{
    SK_MetaDataXMLCreate XmlCreate = new SK_MetaDataXMLCreate();

    // Prompt the dialog, if user clicks in OK it returns true
    if (XmlCreate.prompt())
    {
        XmlCreate.run();
    }
}

Happy DAX-ing 🙂

AX/D365F&O Table indexing

 

Heap:

If table does not have index, that will be called as Heap table.

When the record is inserted into heap table, will be placed in free spaces with out an order.

If query is passed for searching data on any colunm, it will start scaning whole table from the first record one by one record until query engine reaches to actual
record.

This will give significant performance issue if data volume is more in table. Lets have an example for understanding heap.

A heap table is having around 1 million records and time would require to find 1 record is 1 milli second.

A query is passed for searching 20000th record, Query engine would require 20000 milli second to find actual data from heap table. But if you use index in table, it
will give significant performance to fetch actual data row from table. Lets discuss in detail.

Index
An Index is B tree data structure. An index is made up of several pages which is called as nodes.There are three type of nodes will be available in index.
1. Root node

2. Intermediate node

3. Leaf level node

An index is created on table columns. The Root node(Pages) will be having the reference value of the intermediate node and intermediate nodes will be having the reference of the Leaf level node. Leaf level node is called as data pages as actual data of the table is stored in leaf level node. Based on the leaf level pages we can classify the index into two types which is cluster and Non cluster index.Note: Each Data page can have maximum 8KB of data.

Cluster index
A table can have only one cluster index as data can be arranged only one order on columns either ascending or descending.A cluster index will contain reference value from the index column and data in leaf level pages. Please refer below screen shot.
A company has 200 employees and their details(Id and Name) are added to employee master. The cluster index is created on the colunm ‘Id’. Once the index is created, the employee master data organized in a B tree strcture and placed in physical memory of
the computer.

Cluster index
Now you are searching for an employee ‘115’ details using X++ query.

select * from EmployeeMaster
where EmployeeMaster.id == ‘115’;

Once query is submitted to SQL Data base, simultanious process will be applied on that query.

Query execution process:
The query optimizer is piece of software program in SQL which will give different plans to execute above query. The output of the plan is known as estimated execution
plan. Based on estimated execution plan, SQL server find a less cost plan and start executing the query. The executed plan is know as Actual execution plan. The actual
plan will be generated after execution of query.

Note: The query execution is another big topic and we will discuss it in another post. I just gave an overview of query execution above.

Based on estimated plan, the query engine start searching 115 employee id in SQL table.The data searching will be started from root node and traverse to intermediate level and leaf node.As soon as query engine reach root level page, it will compare the value 115 with root node 1, 100
Step 1 = 100 <= 115 = True.
Now query engine traverse toward low level node based on the page pointer in root node.

Step 1 = 100 <= 115 = True.
Step 2 = 110 <= 115 = True.
Step 3 = 120 <= 115 = False.
Since step three is false, query engine move to low level node(Data page) based on the previous row pointer.

Step 4 = 110 <= 115 = True.
Step 5 = 111 <= 115 = True.
Step 6 = 112 <= 115 = False.
Step 7 = 113 <= 115 = True.
Step 8 = 114 <= 115 = True.
Step 9 = 115 <= 115 = True.
Step 10 = 116 <= 115 = False.
Since step 10 is false, Query engine returns previous row value 115, Bren(Name of the employee).
Lets assume, the same employee table has more columns such as Address, ContactNumber…etc.All these details will be saved in Leaf level page. This is beauty of cluster index.
Non Cluster index
A table can have more than one non cluster index. In SQL server 2012 you can create maximum 999 non cluster indexes.A non cluster index will contain reference value from index colunm and address of the row in table.In non cluster index the actual data has been stored in another data page. So compare to cluster index, non cluster index will take extra some spaces for storing
actual data. Also it would require addition effort to find the actual data page. Below picture will give you clear idea how actually non cluster index stores values.

Non Cluster index
There are two scenarios for non cluster indexing structure.

1. Table has non cluster index and don’t have cluster index.
2. Table has non cluster index along with cluster index.

Table has non cluster index and dont have cluster index:-
Now you are searching for an employee ‘115’ details using X++ query.

select * from EmployeeMaster
where EmployeeMaster.id == ‘115’;

Once query is submitted to SQL Data base, simultaneous process will be applied on that query.

Query execution process:
The query optimizer is piece of software program in SQL which will give different plans to execute above query. The output of the plan is known as estimated execution
plan. Based on estimated execution plan, SQL server find a less cost plan and start executing the query. The executed plan is know as Actual execution plan. The actual
plan will be generated after execution of query.

Note: The query execution is another big topic and we will discuss it in another post. I  just gave an overview of query execution above.

Based on estimated plan, the query engine start searching 115 employee id in SQL table.The data searching will be started from root node and traverse to intermediate level and leaf node.As soon as query engine reach root level page, it will compare the value 115 with root node 1, 100
Step 1 = 100 <= 115 = True.
Now query engine traverse toward low level node based on the page pointer in root node.
Step 1 = 100 <= 115 = True.
Step 2 = 110 <= 115 = True.
Step 3 = 120 <= 115 = False.
Since step three is false, query engine move to low level node(Data page) based on the previous row pointer.

Step 4 = 110 <= 115 = True.
Step 5 = 111 <= 115 = True.
Step 6 = 112 <= 115 = False.
Step 7 = 113 <= 115 = True.
Step 8 = 114 <= 115 = True.
Step 9 = 115 <= 115 = True.
Step 10 = 116 <= 115 = False.

Since step 10 is false, Query engine move to Data pages with the help of Address.

Table has non cluster index along with cluster index:-
Now you are searching for an employee ‘115’ deatils using X++ query.

select * from EmployeeMaster
where EmployeeMaster.id == ‘115’;

Once query is submitted to SQL Data base, simultanious process will be applied on that query. Query execution process:
The query optimiser is piece of software program in SQL which will give different plans to execute above query. The output of the plan is known as estimated execution
plan. Based on estimated execution plan, SQL server find a less cost plan and start executing the query. The executed plan is know as Actual execution plan. The actual
plan will be generated after execution of query.

Note: The query execution is another big topic and we will discuss it in another post. I  just gave an overview of query execution above.

Based on estimated plan, the query engine start searching 115 employee id in SQL table.The data searching will be started from root node and traverse to intermediate level and leaf node.As soon as query engine reach root level page, it will compare the value 115 with root node 1, 100
Step 1 = 100 <= 115 = True.
Now query engine traverse toward low level node based on the page pointer in root node.
Step 1 = 100 <= 115 = True.
Step 2 = 110 <= 115 = True.
Step 3 = 120 <= 115 = False.
Since step three is false, query engine move to low level node(Data page) based on the previous row pointer.

Step 4 = 110 <= 115 = True.
Step 5 = 111 <= 115 = True.
Step 6 = 112 <= 115 = False.
Step 7 = 113 <= 115 = True.
Step 8 = 114 <= 115 = True.
Step 9 = 115 <= 115 = True.
Step 10 = 116 <= 115 = False.
Since step 10 is false, Query engine move to Data pages with the help of Address.

In this scenario, Leaf level page doesn’t have reference of actual data page instead it will have reference key of cluster index column value(115).  Now query engine will start finding data using cluster index structure. Please refer below diagram.

NCSenario2

Page Splitting:

An index page can store maximum 8 KB of data.
Before inserting new row into data page, SQL server will check if there is enough free space in data page. For example, A data page is having 7 KB of data already, now SQL server is trying to insert new row with size of 2KB. But only 1 KB free space will be remaining in that data page. Now SQL server will create new data page to store the new row with 2KB size. Once the new row is inserted, SQL server will rearrange the tree based on index column values. Some time it would give significant performance impact during page split. While deleting or updating the data, there would be chance that SQL server will go for page split and reorganise the tree structure. So you should be very careful while designing the index.

Index design:

  1. An index will occupy significant disk space.
  2. If you are frequently doing many data manipulations such as insert, update and delete, don’t create more index  than necessary.
  3. If you use index hint, that may or may not give performance issue. I have explained it in below.
  4. If you are frequently using select query, you can create an non cluster index on columns.

Index hint:

Lets assume a table has two indexes.
1. Idx1
2. Idx2

As per the estimated execution plan SQL server takes 1 ms for fetching the value using index Idx1.
But you are instructing SQL server that(Using indexhint key word on query), use Index Idx2 for fetching the value. But Idx2 would require 2 ms to fetch the same record. So if you are not handling index hint properly, would be facing performance issue.

How should select query be written when use index columns?

Lets take example of salesLine index ‘SalesLineIdx’

There are three columns has been added under index node “SalesLineIdx 

  1. SalesId
  2. LineNum
  3. RecId

In where clause should have same order as below.

Select firstOnly from salesLine
where salesLine.SalesId == <SalesId>
&& salesLine.LineNum == <LineNum>
&& salesLine.RecId == <RecId>

 

Happy DFO-AX ing 🙂

D365O Authentication architecture

Active directory federation service (ADFS)

  • Active Directory Federation Service is service which provides access control and single sign on for all applications including Office 365, cloud based SaaS applications, and other applications on the corporate network.
  • The corporate company ABC is using different applications such as outlook mail, skype, timesheet portal, SharePoint etc.
  • Now employee ‘John’ can access all these applications(App1, App2, App3, App4) by authenticate to one application (App1) with his credentials (john@abc.com).

Single sign on

Single sign on

D365O – Authentication architecture

Architechture

How it works – Real time example

  • All the account details are stored in the active directory. Active directory is also known as Identity provider.
  • Now a user “John” wants to log in into D365 F & O applications. John will open a D365 F & O URL to enter his credentials. The URL is www.D365OnPremise.com.
  • As soon as he browse the URL, log in page gets open where in John will enter his credentials(john@abc.com & password). Once he move away from username field, it will recognize the domain name(abc.com) and the url will be redirected to federation service URL.
  • Now the federation service url will be FD.abc.com. FD is name of the federation service.
  • Then the username and password will be sent back to active directory domain server. The active directory service will validate the credentials and send the response(Claims) to federation server.
  • The federation server will be redirect the url to D365 F & O. Now the url contains the token and claims information’s(www.D365OnPremise.com/token/claim/session). The action of the federation is to generate the security tokens. The federation server is also known as “Claims provider” and “Security token service”.
  • Then D365 application validate token, claim and session details. After the validation the application will trust the federation server & send the start page back to user.

    If you have any suggestions/Corrections, Please feel free to post your comments below.

Monthwise number sequence

Refreshing the number sequencing for sales order each month so that sales order creation month can be identified based on the number.

Initial setup:

m1

///

/// This batch will update the numbersequence format ‘yr/Month/######’ for salesorder number.
///

class Monthwisenumberseq extends RunBaseBatch
{
}

protected boolean canGoBatchJournal()
{
return true;
}

public void run()
{
NumberSequenceReference numberseqref;
NumberSequenceTable numberSeqTable;
container segments;
str annotatedFormat, format, datestr;
;
ttsBegin;
datestr = date2str(systemDateGet(),
321,
DateDay::Digits2,
DateSeparator::Slash, // separator1
DateMonth::Short,
DateSeparator::Slash, // separator2
DateYear::Digits4,
DateFlags::None);

datestr = subStr(datestr, 1, strLen(datestr)- 2);
segments = [[-1,datestr]];
segments += [[-2,’######’]];

annotatedFormat = NumberSeq::createAnnotatedFormatFromSegments(segments);
format = NumberSeq::createAnnotatedFormatFromSegments(segments, false);

numberseqref = NumberSeqReference::findReference(extendedTypeNum(SalesId));

select firstOnly forupdate numberSeqTable
where numberSeqTable.RecId == numberseqref.NumberSequenceId &&
numberSeqTable.NumberSequenceScope == numberseqref.NumberSequenceScope;

if (numberSeqTable)
{
numberSeqTable.AnnotatedFormat = annotatedFormat;
numberSeqTable.Format = format;
numberSeqTable.NextRec = 1;
numberSeqTable.doUpdate();
}
ttsCommit;
}

public static ClassDescription description()
{
return “Monthwise numbersequence”;
}

public server static void main(Args _args)
{
Monthwisenumberseq monthwisenumberseq = new Monthwisenumberseq();

if (monthwisenumberseq.prompt())
{
monthwisenumberseq.run();
}
}

m2

Dimension Look up in AX 2012

SysTableLookup sysTableLookup;
QueryBuildDataSource queryBuildDataSource,queryBuildDataSource1;
Query query;
QueryBuildRange queryRangePhysical,queryRangeTechnical;
DimensionAttribute dimAttr;

select firstonly recid from dimAttr where dimAttr.Name == “Dimension Name(Department or Costcenter,…etc)”
if (dimAttr)
{
sysTableLookup = SysTableLookup::newParameters(tablenum(DimensionFinancialTag), this);

sysTableLookup.addLookupfield(fieldnum(DimensionFinancialTag, value),true);
sysTableLookup.addLookupfield(fieldnum(DimensionFinancialTag, Description),false);
query = new Query();

queryBuildDataSource = query.addDataSource(tablenum(DimensionFinancialTag));
queryBuildDataSource1 = queryBuildDataSource.addDataSource(tableNum(DimensionAttributeDirCategory));
queryBuildDataSource1.relations(true);
queryBuildDataSource1.joinMode(JoinMode::InnerJoin);
queryBuildDataSource1.addLink(fieldnum(DimensionFinancialTag, FinancialTagCategory), fieldnum(DimensionAttributeDirCategory, DirCategory));

queryBuildDataSource1.addRange(fieldnum(DimensionAttributeDirCategory,DimensionAttribute)).value(queryValue(dimAttr.RecId));
sysTableLookup.parmQuery(query);

sysTableLookup.performFormLookup();
}

Printing dimension value of customer or vendor.

static void ShowcustDefaultDimensions(Args _args)
{
CustTable custTable;
DimensionAttributeValueSet dimAttrValueSet;
DimensionAttributeValueSetItem dimAttrValueSetItem;
DimensionAttributeValue dimAttrValue;
;

custtable = Custtable::find(‘Customer Id’);

while select dimAttrValue
exists join dimAttrValueSetItem
exists join dimAttrValueSet
where dimAttrValueSet.RecId == custTable.DefaultDimension &&
dimAttrValueSetItem.DimensionAttributeValueSet == dimAttrValueSet.RecId &&
dimAttrValue.RecId == dimAttrValueSetItem.DimensionAttributeValue
{
print dimAttrValue.getValue();
}
pause;
}