Monday, December 15, 2014

Record caching principle

Which statement will perform faster?

select inventTable
    join inventItemGroup
    where inventItemGroup.ItemGroupId == inventTable.ItemGroupId
    && inventTable.ItemId == 'A'; 


or
select inventTable
    where inventTable.ItemId == 'A';

    select inventItemGroup
    where inventItemGroup.ItemGroupId == inventTable.ItemGroupId;
 


From the first point of view it seems obvious that the first one with join, since it will result in only one database call and the second will result in two. However, it is not always the case. For example, if records that are being looked up in the second example are cached on the server (or even on the client) there will be no database calls at all, and may be even no client\server calls! The code from the first example will never hit the cache since caching is not supported for the queries with joins.
Actually Dynamics AX caches records by their primary key value. So, if a query has no joins and has primary key in its where clause then it will hit the cache. Otherwise it won't.

Unit tests data setup and tear down

There are methods in AX to setup data before test execution and tear it down afterwards - SysTestCase.setup() and SysTestCase.tearDown(). Those methods will be executed before and after each test method in a unit test class. 
In AX 2009 two new methods were added - setupTestCase() and tearDownTestCase() which are executed only once per unit test class execution - before first test method and after last test method. But there is a problem with using them - by default each unit test method runs in a separate instance of the unit test class, so if you are setting up some data in the setupTestCase() method there will be no possibility to store a reference to it in the instance variable. 
To change this behavior useSingleInstance() method can be overridden to return true. In this case all test method as well as setupTestCase() and tearDownTestCase() method will be execute in one instance of the unit test class.

Surrogate keys in AX 2012

Data modeling enhancements were prioritized really high in AX 2012. I already mentioned one earlier – table inheritance, but there are a lot of other important features about data modeling.

One such feature is the support of surrogate keys. Surrogate keys recommended themselves as a very good solution for complex databases because of their beneficial properties, like immutability, robustness to redesign and performance. And now this best practice is coming to AX. It is the recommendation for AX 2012 to use surrogate keys for all new tables, unless there is a good reason not to do so.

Actually, previous versions of AX already have surrogates – RecIds. And it was possible to introduce surrogate key just by setting CreateRecIdIndex table property to Yes. But there is a problem with the inconvenience of working with surrogates in the UI. Imagine InventTable using surrogate key and all other tables that have relations to InventTable using its surrogate key as a foreign key. Then on any form where ItemId should be displayed (let’s say on the SalesTable) an extra join to InventTable will be needed to fetch the ItemId. And what if user can change the item? Then even more custom logic is needed to resolve the entered ItemId to the corresponding surrogate key value and write it to the SalesLine.

This inconvenience actually was a showstopper. But not anymore. AX 2012 got kernel support for surrogate key substitution. And not only in forms, but in Axd document services and even in the debugger.

Let me show an example of how it works:
UnitOfMeasure table uses surrogate key as a primary key. However, the user-friendly identifier of the UnitOfMeasure table records is the UnitOfMeasureSymbol field. This field is a part of the SymbolIdx index, which is specified as a replacement key on the UnitOfMeasure table.


This setup basically means that surrogate key values (RecIds) of the UnitOfMeasure table records will be automatically substituted in the UI with the replacement key values (UnitOfMeasureSymbols).
UnitOfMeasureConversion table has a relation to the UnitOfMeasue table through the surrogate key.


As you can see, on the UnitOfMeasureConversion form there are only UnitOfMeasureConversion datasources. And you can notice new ReferenceGroup control which is bound to the FromUnitOfMeasure field (which contains surrogate values).


And in the UI it will look just like UnitOfMeasureSymbol field is shown instead. And it can also be modified and that will be handled automatically, without a single line of code.


Actually ReferenceGroup control has much more interesting capabilities and thus deserves a separate post.

Unit of Work

After the post about surrogate keys I was asked the following question:
There is the following problem with using surrogate keys.
A record gets RecId value at the moment it is inserted into the database. What if we are doing bulk insert of a journal with many lines and the journal header contains some totals calculated based on its lines. It is impossible to insert lines before header since the value of the journal's surrogate key is unknown. But that would be so convenient because otherwise the header should be inserted first, then the lines and then the header should be updated with the calculated totals.


The answer is – use Unit of Work. It allows to perform create, update and delete operations without worrying about the order of those and without a need to specify surrogate key values. It will all be done automatically in the kernel!

Example:
Let we have the following tables:


The following job will insert the journal header with calculated totals and 10 lines. Note, that there is no place where surrogate keys are mentioned in the code. Business data only.


And here is the result.



Some more details about the unit of work feature:
  1. All database operations happen only when saveChanges() method is invoked.
  2. UnitOfWork class has insertOnSaveChanges(), updateOnSaveChanges() and deleteOnSaveChanges() methods for CUD operations.
  3. Surrogate keys can be propagated automatically to related records if buffers are linked via navigation methods (AJournalLine.header() method in the example). Navigation methods can be created automatically from relations. I’ll write a separate post about them.

And the last but not least. AX client has the great support of the unit of work as well. This means that form datasources can be grouped into a unit of work, so all the benefits of simultaneous commit and surrogate key propagation will be available on forms. There are a lot of possibilities and tricks there, so this topic worth one or more dedicated posts as well.

How can I use polymorphism using X++?

Polymorphism is not the same as method overloading or method overriding. Polymorphism is only concerned with the application of specific implementations to an interface or a more generic base class. Method overloading refers to methods that have the same name but different signatures inside the same class. Method overriding is where a subclass replaces the implementation of one or more of its parent's methods. Neither method overloading nor method overriding are by themselves implementations of polymorphism.
polymorphism in the context of object-oriented programming, is the ability of one type, A, to appear as and be used like another type, B.

So, basically, any RunBaseBatch extension class (the ones you use to process stuff in AX) is an example of polymorphism in AX. Because they all call the methods of the base class as if they were their own.


 method overloading is not suppoorted in X++.
Instead, we define methods with different names and a specific argument set:
newFromInventTable(InventTable _it)
newFromPurchLine(PurchLine _pl)
newFromSalesLine(SalesLne _sl)

Import customer address in the Global Address Book

In AX 2012 the GAB (Global Address Book) has gone through some serious changes and now the address information for the customers and vendors is kept in the LogisticsPostalAddress,and LogisticsPostalAddressExt tables. Additional information is maintained in the LogisticsAddress* tables.

This is the simplest possible example of how to create new customer and populate the customer's address information in AX 2012. Note that you may have to modify this code in order to handle the creation of records in the LogisticsAddress* tables.

        CustTable                       custTable;
        AccountNum                      accountNum = 'ABC-123';
        CustGroupId                     custGroupId = '10';
        Name                            name = 'ABC';
        
        DirParty                        dirParty;
        DirPartyPostalAddressView       dirPartyPostalAddressView;


        ttsBegin;

        custTable.clear();
        custTable.initValue();

        custTable.AccountNum = accountNum;
        custTable.CustGroup = custGroupId;

        custTable.insert(DirPartyType::Organization, name);

        dirParty = DirParty::constructFromCommon(custTable);

        dirPartyPostalAddressView.LocationName = 'ABC-AUTO';
        dirPartyPostalAddressView.City = 'London';
        dirPartyPostalAddressView.Street = 'Dover Street';
        dirPartyPostalAddressView.StreetNumber = '123';
        dirPartyPostalAddressView.CountryRegionId = 'GBR';

        dirParty.createOrUpdatePostalAddress(dirPartyPostalAddressView);

        ttsCommit;

Form control type at run-time

This shows how to get the type of a form control at run-time. The code iterates through the controls in a field group and checks each control for it's type. 

    FormControl fc;
    int         i;

    // ...
   
    for(i=1; i <= FieldGroupName.controlCount(); i++)
    {
        fc = FieldGroupName.controlNum(i);
        if(SysDictClass::is(fc, classNum(FormStringControl)))
        {
            info(strFmt("%1 %2", fc.name(), fc.type()));
        }
    }
If you do this in Dynamics AX 2012 you will get the following warning:

The SysDictClass::is method is obsolete. Use the IS operator instead.


The new syntax is:

if(fc is FormStringControl)