A while ago I wrote about reflection in Dynamics Ax. In the article I mentioned that it was also possible to modify the AOT and that I’d give some examples. Well, I kind of forgot. Sorry about that. Todd Hensley was kind enough to remind me of that promise and presented an interesting example problem.
I have a list of tables where I want to set the ClusterIndex to be the same as the PrimaryIndex.
I’ve been able to loop through an array of the table names, look them up using DictTable, and examine the PrimaryIndex and ClusterIndex values.
But I can’t figure out how I would say:
myTable.clusterIndex = myTable.primaryIndex;The properties all appear to be read-only.
Any ideas?
Thank you for the question and, yes, I do have an idea.
Enter the TreeNode
The key to solving this is knowing that there is more than one way to do reflection. As Todd found out, the Dictionary API is read-only. But the TreeNode API is not.
TreeNode is exactly what its name says: a node in a tree.
I can hear you thinking: “Nodes? Tree? What are you talking about?”
I’m talking about the Application Object Tree. The AOT has a bunch of nodes and a TreeNode object can represent any one of those nodes, like Forms, a class method or a table.
Whenever you’re using the AOT, clicking on an object and setting properties you’re using TreeNodes. And the best part is you can do it in code too!
Getting the node
Enough talk. Time to show the code. First you need to get hold of a node. Let’s use CustTable as an example.
TreeNode node = TreeNode::findNode(@'Data dictionaryTablesCustTable'); ; info(node.treeNodePath()); info(node.AOTname()); |
As you can see, you need to enter the path similar to the way you open the nodes AOT. If findNode() fails it will return null.
Once you have the node the fun starts. In this case I’m just dumping something in the infolog to show you it works.
Properties
Now let’s do what we really want. We’re going to set the primary index as the cluster index.
TreeNode node = TreeNode::findNode(@'Data dictionaryTablesCustTable'); str indexName; #Properties ; indexName = node.AOTgetProperty(#PropertyPrimaryIndex); node.AOTsetProperty(#PropertyClusterIndex, indexName); node.AOTsave(); node.treeNodeRelease(); node = null; |
There are a couple of things to explain here. First of all the macro #Properties contains the names of all properties you can read and write. These are the same as what you get in the property window. Use the macro instead of literal strings, it’s safer and doesn’t violate best practices.
Next you can see there is a set of methods to get and set properties. After modifying anything you need to save the changes, just like you would when changing an AOT object in the property window. TreeNode works just like doing it manually.
Finally you need to release the memory held by the TreeNode. The garbage collector cannot clean up TreeNode objects by itself. It needs a little help from you by calling treeNodeRelease() when you’re done. In this example nothing bad would happen if you don’t. However, if you run this in a loop a few thousand times Ax will run out of memory. Not something you really want.
Fitting it in with DictTable
Todd already did a lot of work with DictTable to find the tables he needs to change. And now I tell him DictTable won’t cut it. Not really helping.
Not all is lost. DictTable has a method to get a TreeNode object. How convenient. If we change the above example to start with a DictTable object we end up with this:
SysDictTable dictTable; TreeNode node; str indexName; #Properties ; dictTable = SysDictTable::newTableId(tableNum(CustTable)); node = dictTable.treeNode(); indexName = node.AOTgetProperty(#PropertyPrimaryIndex); node.AOTsetProperty(#PropertyClusterIndex, indexName); node.AOTsave(); node.treeNodeRelease(); node = null; |
This can be adapted to fit in a loop over several tables and Todd’s problem is solved.
You can do a lot more fun stuff with the TreeNode API. I’ll cover that in another installment. I won’t forget this time. I promise 🙂
Also, if you have questions or ideas for articles don’t hesitate to let me know.
Björn,
Thanks for the great article. Nice and concise.
In my case, I also used the SqlDataDictionary.tableSynchronize method to force the index change on the underlying database.
Since I was starting with a list of table names I also found the tablename2id function to be very handy.
twh
Hi Björn,
i have tried to change the Id of a LicenseCode with this code:
UtilIdElements utilId;
TreeNode node;
#Properties
;
select forupdate utilId where utilId.utilLevel == UtilEntryLevel::vap && utilid.recordType == UtilElementType::LicenseCode && utilid.id == 30001
{
node = xUtilIdElements::getNode(utilId);
node.AOTsetProperty(#PropertyId,50999);
node.AOTsave();
node.treeNodeRelease();
}
info(“Ende”);
in the line node.AOTsetProperty(#PropertyId,50999); i get a Stack-Trace
Meldung (18:37:01)
Stack-Trace: Die Eigenschaft wurde nicht gefunden. (the property could not be found)
(C)ClassesTreeNodeAOTsetProperty
(C)JobsTest – line 15
Do you have any idea why it doesn’t work?
hi,
is it possible to set the object id?
Using treenode AOTsetProperty(#PropertyId,..) returns an error.
thx
I haven’t tried it myself, but I would expect this to fail. Object IDs are managed by the kernel and cannot be assigned manually. Well, maybe you can if you mess around with XPO files and import with object ID. But generally it’s off limits.