Extracting Table Information and Exporting to CSV in Dynamics 365 using X++ and TreeNode

Hello DAX DEV,

In this blog post, we’ll be diving into an X++ class called FNGTableInfoHelper Created to extracts information about tables with a specific prefix and without the “Staging” suffix. The class gathers specific details about these tables and their fields in Dynamics 365 Finance and Operations and exports the data to a CSV file using the TreeNode class.

The class code is available in this blog post, hosted in github, To execute the class one should use SysClassRunner with the class name in the URL, the system will hang for a while to get the full list of tables than extracts table by table with the users ok, the result of the execution is a series of CSV files:

You’ll choose a directory and save the files. process takes 3-10 min depending on the amount of tables, in my case my 64 tables got extracted in less than 5m.

CSV Files are as following:

Below is an overview of the entire FNGTableInfoHelper class:
internal final class FNGTableInfoHelper
{
void showFieldInfo(TableId tableId)
{
int i,j;
SysDictType dictType;
SysDictEnum dictEnum;
commaStreamIo iO = commaStreamIo::constructForWrite();
filename filename = strFmt("%1.csv",tableId2PName(tableId));
DictTable dictTable = new DictTable(tableId);
DictField dictField;
str lastFieldName;
TableName tableName = dictTable.name();
FieldName fieldName;
str label;
container writingContainer;
str getEnumValues(DictEnum _dictEnum)
{
str ret;
int enumValue;
for (enumValue = 0; enumValue <= _dictEnum.values(); enumValue++)
{
str enumElement = strFmt("%1", _dictEnum.index2Symbol(enumValue));
str enumLabelId = strFmt("%1", _dictEnum.index2LabelId(enumValue));
str enumLabel = strFmt("%1", SysLabel::labelId2String(_dictEnum.index2LabelId(enumValue)));
ret += enumElement ? (strFmt(" [%1:%2-%3-%4] |", enumValue,enumElement,enumLabelId,enumLabel)) : "";
}
ret = subStr(ret,1, strLen(ret) - 2) + ")";
return ret;
}
str if_enum()
{
return (dictEnum == null ? "" : "Enum " + dictEnum.name() + ": ("+ getEnumValues(dictEnum));
}
void setTableHeaderInfo()
{
#Properties
TreeNode treeNode = TreeNode::findNode(@"\Data Dictionary\Tables").AOTfindChild(tableName);
iO.writeExp(["TableName:",tableName]);
iO.writeExp(["Label:",tableId2PName(tableId)]);
iO.writeExp(["ConfigurationKey:",treeNode.AOTgetProperty(#PropertyConfigurationKey)]);
iO.writeExp(["PrimaryIndex:",treeNode.AOTgetProperty(#PropertyPrimaryIndex)]);
iO.writeExp(["TitleField1:",treeNode.AOTgetProperty(#PropertyTitleField1)]);
iO.writeExp(["TitleField2:",treeNode.AOTgetProperty(#PropertyTitleField2)]);
iO.writeExp(conNull());
iO.writeExp(conNull());
}
setTableHeaderInfo();
writingContainer = ["Number","Field Name","Label","Allow edit",
"Mandatory","Type","Help Text"];
iO.writeExp(writingContainer);
writingContainer = conNull();
dictField = dictTable.fieldObject(dictTable.fieldCnt2Id(i));
while (dictField)
{
i++;
lastFieldName = fieldName;
fieldName = dictField.name();
if (lastFieldName == fieldName)
{
break;
}
label = dictField.label();
if (!dictField.isSystem())
{
j++;
Types type = dictField.baseType();
str isMandatory = dictField.mandatory() ? "Yes" : "No";
str allEdit = dictField.allowEdit() ? "Yes" : "No";
dictType = new SysDictType(dictField.typeId());
dictEnum = new SysDictEnum(dictField.enumId());
str typeStr =
dictType == null
? if_enum()
: strFmt("%1 %2 %3",dictField.name(), type,
dictField.stringLen()
? strFmt("Len: %1", dictField.stringLen())
: if_enum());
writingContainer = [j,fieldName, label, allEdit, isMandatory, typeStr, dictField.help()];
iO.writeExp(writingContainer);
}
dictField = dictTable.fieldObject(dictTable.fieldCnt2Id(i));
}
System.IO.Stream stream = iO.getStream();
stream.Position = 0;
System.IO.StreamReader reader = new System.IO.StreamReader(stream);
str csvFileContent = reader.ReadToEnd();
File::SendStringAsFileToUser(csvFileContent, filename);
info(strFmt("CSV file %1 Sent to user", filename));
}
static FNGTableInfoHelper construct()
{
return new FNGTableInfoHelper();
}
public static void main(Args args)
{
void extract(tableName tableName)
{
str message = 'Extract field info of %1 Table';
if (box::yesNo(strFmt(message,tableName),DialogButton::Yes) == DialogButton::Yes)
{
FNGTableInfoHelper::construct().showFieldInfo(tableName2Id(tableName));
}
}
container tables = FNGTableInfoHelper::getFNGTables();// ["FNGJobTable","FNGJobLine","FNGJobServiceReports","FNGJobActivities","FNGCustomerAssetTable","FNGCustomerAssetJobTable","SalesTable","SalesLine"];
while (conLen(tables))
{
extract(conPeek(tables,1));
tables = condel(tables,1,1);
}
}
public static container getFNGTables()
{
TreeNode tablesNode = TreeNode::findNode(@"\Data Dictionary\Tables");
TreeNodeIterator iterator;
TreeNode tableNode;
container tableNames = conNull();
int i,time = timenow();
if (tablesNode)
{
iterator = tablesNode.AOTiterator();
tableNode = iterator.next();
while (tableNode)
{
str name = tableNode.AOTname();
int len =strLen(name);
str prefix = subStr(name,1,3), sufix = subStr(name, len - 6, 8);
if (prefix == "FNG" && sufix != "Staging")
{
tableNames = conIns(tableNames, conLen(tableNames) + 1, name);
}
tablesNode = null;
tableNode = iterator.next();
}
iterator = null;
}
str message = (strFmt("FNG tables collected in %1 sec",timeNow()-time));
info(message);
return tableNames;
}
}

Key methods in the class include:

  1. showFieldInfo(TableId tableId): This method extracts table and field information based on the given table ID, writes it to a CSV file, and sends the file to the user. The method uses the DictTable and DictField classes to access the table and field information. It also writes the table header information by calling the setTableHeaderInfo() method.
  2. getEnumValues(DictEnum _dictEnum): This is a nested method within showFieldInfo(). It takes a DictEnum object as an argument and returns a formatted string with all enumeration values and labels for the given enumeration object.
  3. if_enum(): Another nested method within showFieldInfo(). It checks if the dictEnum object is not null, and if so, it returns a formatted string containing the enumeration name and the result of calling getEnumValues(dictEnum).
  4. setTableHeaderInfo(): This nested method within showFieldInfo() retrieves and writes the table header information using the TreeNode class to navigate the Application Object Tree (AOT) and access table properties.
  5. construct(): This static method is a constructor for the FNGTableInfoHelper class. It returns a new instance of the FNGTableInfoHelper class.
  6. main(Args args): This static method is the entry point of the class. It defines a local method called extract() that takes a table name as an argument and calls showFieldInfo() with the table ID for the given table name. The main() method then loops through a container of FNG tables, calling the extract() method for each table.
  7. getFNGTables(): This static method retrieves all tables with the “FNG” prefix and without the “Staging” suffix. It uses the TreeNode class to navigate the AOT, collecting table names in a container. The method returns the container of table names.
  8. These methods work together to extract information about FNG tables and their fields, then export the data to a CSV file using the TreeNode class for AOT navigation.

The class’s entry point, main(Args args), calls the extract method for each table name in the tables container. The extract method uses the showFieldInfo method to get the table information and writes it to a CSV file.

The TreeNode class is used to navigate the Application Object Tree (AOT) and access table information. For example, in the setTableHeaderInfo() and getFNGTables() methods, TreeNode objects are used to find table nodes and retrieve the necessary properties.

With the help of the FNGTableInfoHelper class, you can easily generate CSV files containing information about specific tables in the Dynamics AX system. This can be particularly useful for developers and administrators working with Dynamics 365 Finance and Operations.

That’s all for today’s post. We hope you find the FNGTableInfoHelper class helpful for extracting table information and exporting it to CSV files in your Dynamics 365 projects. Happy coding!