Skip to content

Replacement groups in AX 2012

AX 2012 introduced a new type of form control called “Replacement group”. It’s very handy, nevertheless quite a few developers still don’t know about it or are not sure how to use it effectively.

This post is not going to details; the intention is rather to show something simple, though still from end to end.

Let’s say that we want to create a form showing released products. We create a form with InventTable as the data source and with a grid.

We’re especially interested in the Product field, so we drag it from the data source and drop it to the grid.

Form-Product

Notice that the type of the control is ReferenceGroup – we’ll talk about it very soon.

Without changing anything, we can run the form; it successfully displays product numbers:

Let’s say that users insist on working with product names instead of product codes. Maybe they should have used a better convention for product codes, but that’s another topic. Now we have meet the request.

What should we do? Providing an edit method that would display names and find IDs from names specified by users? No, it’s much simpler with reference groups. Open properties of the reference group and change the value of ReplacementFieldGroup property from AutoIdentification to ProductInformation:

ReplacementFieldGroup

Save the form and open it again – it now shows product names. It’s that simple!

ProductNames

If the product was editable (which is not normally the case in InventTable), you would also get a lookup and would be able to select (or type) product names:

LookupItemName

You could also override the lookup, if you don’t like the default one.

Now we should look more closely at how it works.

First of all, look at the Product field in InventTable. Its base type is Int64 and extended data type is EcoResProductRecId. Properties of the EDT shows that ReferenceTable = EcoResProduct, therefore the Product field contains record IDs of EcoResProduct table.

If we used the Int64Edit control to display the Product field, we would see just numbers, which wouldn’t be very useful.

RecIds

That’s why we have Reference group controls. The reference group in our form is bound to the Product field (set in ReferenceField property), so that’s what gets saved to database. But we don’t display it to users – instead of that, we use a replacement field group. Field groups are defined on tables in AOT – here we can see the groups of InventTable that we used in our example:

InventTable-fieldGroups

AX looks at the field (or even fields) contained in the field group and displays them instead of the record ID. If you change a value of the replacement field, AX finds the corresponding RecId and put it to the underlying field.

As you can see, reference groups allow you to change fields displayed to users by a simple property change. Also notice that it has zero effect to data stored in database – it still refers to a record ID.

There is one last thing I would like to mention, because it often confuses people. You may have a reference group for a worker showing the worker name (e.g. the Sales responsible person in Sales order form). The control uses the AutoIdentification field group of the HcmWorker table. The group contains a single field, Person, which is a RecId of DirPerson table. AutoIdentification group on DirPerson is empty, so where the name comes from? The trick is that DirPerson table inherits from DirPartyTable, which contains the Name field in its AutoIdentification group. AX deals with all this complexity for you.

XML DocType in X++

Let’s say that we want to use X++ to create an XML file with the following DocType definition:

<!DOCTYPE MyType SYSTEM "http://www.validome.org/check/test.dtd">

AX has Xml* classes (e.g. XmlDocument) for such purpose, but let’s build it with .NET classes first (you’ll see why in a moment). This is X++ code calling .NET classes through .NET Interop:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', null, 'http://www.validome.org/check/test.dtd', null));
info(xmlDoc.get_OuterXml());

The output is exactly what we want.

Now let’s try to rewrite it with native X++ classes:

XmlDocument xmlDoc = new XMLDocument();
xmlDoc.appendChild(xmlDoc.createDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.xml());

Notice that it’s very similar to the previous example, but we had to replace null values with empty strings (”), because X++ doesn’t support null-valued strings.

The output is not correct in this case:

<!DOCTYPE MyType PUBLIC "" "http://www.validome.org/check/test.dtd"[]>

We can easily prove that the empty strings are to blame by rewriting our .NET-based example to use empty strings instead of nulls:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.get_OuterXml());

The output is identical to what we get from X++.

Let me explain what’s going on.

If you provide a value for publicId (the second parameter), the PUBLIC keyword is used, followed by the URI provided in publicId. That applies to empty URI as well, so we get:

<!DOCTYPE MyType PUBLIC "" (…)>

If you want to get SYSTEM keyword, publicId must be null, not “”. The fourth parameter, internalSubset, is a similar case.

Even if you aren’t familiar with .NET at all, you likely understand the difference between a null reference (x = null) and an empty object (x = new Object()). Unlike in X++, strings in .NET are objects (instances of System.String class) and therefore they can either refer to a value or be without any value at all (= null).

Now what we can do to set null values to XmlDocument.createDocumentType()? Well… nothing. All parameters are X++ strings and null isn’t a valid value for them. It would have to be designed differently, e.g. using strings wrapped in objects or providing an additional flag to say that the value should be ignored.

The workaround is simple – you can use .NET classes (from System.Xml namespace) as in the first code snippet.

Tested in AX 2012 R3.

Operand types are not compatible

I managed to produce a really confusing error in AX 2012. Look at the following piece of code:

MyExtendedDataType a, b;
a = b;

Pretty boring, isn’t it? There is nothing to fail… except that I got a compilation error: Operand types are not compatible with the operator. What?

Actually it become quite clear as soon as I simplified my code to this trivial example. There is clearly nothing wrong with my code, it must have something to do with the data type.

To make a long story short, I created a new extended data type of type enum and forgot to fill in the EnumType property. Linking the EDT to an enum instantly solved the problem.

But beware – the compiler won’t detect many other uses of this incorrectly-defined type. For example, MyExtendedDataType a = NoYes::Yes; is compilable X++ code even if MyExtendedDataType has empty EnumType.

Compile backwards

Today I noticed that Type hierarchy browser in AX 2012 R3 allows you to compile types backwards:

HierarchyBrowserCompile

Context menu in AOT supports only forward compilation.

It’s possible that the feature is there for some time and it just took me long time to notice it, because I normally don’t compile code from Type hierarchy browser.

Stakeholder license for Visual Studio Online

Brian Harry announced some upcoming changes in licensing of Visual Studio Online (and later for Team Foundation Server too). Everybody should be delighted by the new Stakeholder license, which will be completely free. It will have following permissions:

  • Full read/write/create on all work items
  • Create, run and save (to “My Queries”) work item queries
  • View project and team home pages
  • Access to the backlog, including add and update (but no ability to reprioritize the work)
  • Ability to receive work item alerts

That’s a lot of things that people can do without any cost, isn’t it? Now everybody will be able to participate in a VS Online / TFS project without any special licensing requirements.