Monday, September 2, 2019

Search Projects where an Element Is Used in Dynamics Ax using x++ Reflection

today I need to search in which project an object was customized and ask for the relevant FDD. It is always a good practice to add some notes when you customize code, but not everybody does… So I found in the book Microsoft Dynamics AX 2012 R3 Development Cookbook on pag 333 a good recipe. But unfortunately the object I was looking for was in a deeper node and this recipe did not work.

Let me first bring the original recipe for Searching for an object in a development project from the book

In Dynamics AX, any development changes to the application normally have to be organized
in development projects. The same object could belong to one or more projects, but Dynamics
AX does not provide an easy way to determine which development projects a specific object
belongs to.
In this recipe, we will create a class to search for an object in the development projects.
The class is only for demonstration purposes, but it can be easily converted to a standalone
tool and integrated into the right-click menu.

In the AOT, create a new class with the following code snippet:

class EF_SearchProjectsElementIsUsed
{
}

public static void main(Args _args)
{
    EF_SearchProjectsElementIsUsed::TestDevProjectSearch();
}

static server void TestDevProjectSearch()
{
    EF_SearchProjectsElementIsUsed search;
    search = new EF_SearchProjectsElementIsUsed();
    search.find(UtilElementType::Table, tableStr(CustTable));
}

void find(UtilElementType _type, IdentifierName _name)
{
    TreeNode projects;
    ProjectNode project;

    projects = SysTreeNode::getSharedProject();
    if (!projects)
    {
        return;
    }

    project = projects.AOTfirstChild();

    while (project)
    {
        if (this.findChildren(
                project.loadForInspection(),
                _type,
                _name))
        {
            info(project.AOTname());
        }
        project = project.AOTnextSibling();
    }
}

private boolean findChildren(
            TreeNode _parent,
            UtilElementType _type,
            IdentifierName _name)
{
    TreeNode child;
    TreeNodeIterator iterator;
    #TreeNodeSysNodeType
    iterator = _parent.AOTiterator();
    child = iterator.next();

    while (child)
    {
        if (child.treeNodeType().id() == #NT_PROJECT_GROUP)
        {
            return this.findChildren(child, _type, _name);
        }
        else if (child.AOTname() == _name &&
                        child.treeNodePath() &&
                        child.utilElement().recordType == _type)
        {
            return true;
        }
        child.treeNodeRelease();
        child = iterator.next();
    }
    return false;
}
How it works...

In this recipe, we create a new class with several methods. The first method is
findChildren() and is used for a recursive search operation within the AOT node. It
accepts three parameters: a TreeNode object, an element type, and an element name. In
this method, we go through all the children of the TreeNode object and check whether any
of them match the provided element type and name. If any of the child nodes contain more
nodes within, we use the same findChildren() method to determine whether any of its
children match the element type and name.
The second method is named find() and is used for the actual search, for the given element
type and name. The method goes through all of the shared development projects and calls the
findChildren() method to determine whether the given element is in one of its nodes.
The class can be called from anywhere in the system, but in this recipe, to demonstrate how it
works, we create a new job, define and instantiate the class, and use the find() method to
search for the CustTable table in all the shared projects.

Mine enhanced recipe using a dialog and TreeNodeTraverser to get also child nodes containing the object

I also added a filter to search only in projects belonging to a specific layer. So I can just search to which project in cus layer the object belong. This speeds the search quite significantly.

class EF_SearchProjectsElementIsUsed
{
}
public static void main(Args _args)
{
    Dialog dialog;
    DialogField elementType, layer, elementName;
    UtilElementType type;
    IdentifierName name;
    UtilEntryLevel projectLayer;

    dialog = new Dialog('Search element in projects');

    elementName = dialog.addField(extendedTypeStr(IdentifierName));
    layer = dialog.addField(enumStr(UtilEntryLevel), 'Project Layer');
    elementType = dialog.addField(enumStr(UtilElementType), 'Type');
    dialog.addText("NOTE: setting the type will traverse the node and search in child nodes");

    elementName.control().mandatory(true);

    if (dialog.run() && elementName.value() != '')
    {
        type = elementType.value();
        name = elementName.value();
        projectLayer = layer.value();

        EF_SearchProjectsElementIsUsed::TestDevProjectSearch(type, name, projectLayer);
    }


}
static server void TestDevProjectSearch(UtilElementType _type, IdentifierName _name, UtilEntryLevel _projectLayer)
{
    EF_SearchProjectsElementIsUsed search;
    search = new EF_SearchProjectsElementIsUsed();
    search.find(_type, _name, _projectLayer);
}
void find(UtilElementType _type, IdentifierName _name, UtilEntryLevel _projectLayer)
{
    TreeNode projects;
    ProjectNode project;
    Set objectsInProject;
    SetEnumerator objectsInProjectEnumerator;
    str objectInProjectPath;

    projects = SysTreeNode::getSharedProject();
    if (!projects)
    {
        return;
    }

    project = projects.AOTfirstChild();

    setprefix(strFmt('These projects contain %1', _name));

    while (project)
    {
        /* I just want to search in cus layer, so is faster.
        to search all remove the condition */
        if (project.AOTLayer() != _projectLayer)
        {
            project = project.AOTnextSibling();
            continue;
        }

        objectsInProject = this.findChildren(
                                    project.loadForInspection(),
                                    _type,
                                    _name);

        if (objectsInProject.elements())
        {
            setprefix(project.AOTname());
            objectsInProjectEnumerator = objectsInProject.getEnumerator();

            while (objectsInProjectEnumerator.moveNext())
            {
                objectInProjectPath = objectsInProjectEnumerator.current();
                info(objectInProjectPath);
            }

        }
        project = project.AOTnextSibling();
    }
}
private Set findChildren(
            TreeNode _parent,
            UtilElementType _type,
            IdentifierName _name)
{
    TreeNodeTraverser tnt;
    TreeNode treeNode;
    UtilElements utilElements;
    TreeNodeType nodeType;
    boolean isRoot;
    str treePath;
    boolean nodeTypeIsUtilElement;
    boolean isTreePath;
    boolean showObject;
    Set objectsInProject;

    tnt = new TreeNodeTraverser(_parent);
    objectsInProject = new Set(Types::String);

    while (tnt.next())
    {
        treeNode = tnt.currentNode();
        nodeType = treeNode.treeNodeType();
        showObject = true;

        if (_type)
        {
            showObject = false;
            nodeTypeIsUtilElement = nodeType.isUtilElement();
            isTreePath = treeNode.treeNodePath() ? true : false;
            isRoot = nodeType.isRootElement();

            if (nodeTypeIsUtilElement && isTreePath && isRoot)
            {
                utilElements = treeNode.utilElement();//need the above conditions otherwise treeNode.utilElement() might fail
                if (utilElements.recordType == _type)
                    showObject = true;
            }
            //Debug::printDebug(strFmt('x: %1 - %2', showObject, treeNode.AOTToString()));
        }


        if (treeNode.AOTname() == _name && showObject)
        {
            objectsInProject.add(strFmt('%1 - (is root: %2)', treeNode.AOTToString(), nodeType.isRootElement()));
        }

        treeNode.treeNodeRelease();
    }
    return objectsInProject;
}

this how it would look like without setting the type. (note that it finds every object, tables forms, menu items… and also child objects like form datasources)

image

image

while setting the type it will filter per object type.

image

image

No comments:

Post a Comment