2015 m. birželio 17 d., trečiadienis

AX Source Code Quality: Top 10 X++ Coding Commandments 1.1

Hello AX Word,

This time I came up with an idea to list top 10 coding guidelines. We would all write a better code if we followed them.

      0. You shall check your code for best practices.
  1. You shall use a proper case for X++ code: start variables, functions, keywords with a lower letter; start application elements with a capital letter.
  2. You shall indent you code appropriately.
  3. You shall add curly brackets around every if, else, loop, switch (except case) and while select statement.
  4. You shall use meaningful names of variables, functions and application elements.
  5. You shall add a single spaces around operators, after commas, if, switch, case, loop keywords.
  6. You shall not use spaces before function parentheses, case's semi-column, ++ and -- operator, after ! (exclamation mark).
  7. You shall avoid using hard-coded values, especially for interface texts.
  8. You shall breakdown long statements.
  9. You shall use column style alignment.
  10. You shall cleanup unused code.
The list is subject to change if I get a more valuable best practice than one above. So this was named 1.0 :)

P.S. I wonder if there is beautifier tool somewhere in the market?

Be aware and take care!

2015 m. birželio 8 d., pirmadienis

AX Source Code Quality: Complex Select Statement Layout

Hello AX World,

One of the key factors that make your code look ugly is layout. There is a very good guidance for that on MSDN site. Everything looks clear when you read it. But when it comes to coding you start wondering how should I make this select statement adhere to best practices? How should I align this complex where clause?

My basic rules are:

  1. Follow MSDN guidelines for select statement.
  2. Use proper indentation.
  3. Keep field list compact.
  4. Make where clause look nice.

Follow MSDN guidelines for select statement.

It also includes while select, update_recordset and delete_recordset.

There are some rules which I am not big fan of.
First is that boolean operators should be placed at the beginning of the line. Well, even standard code does not always adhere to that rule.

Does this look nice?
When I try to write boolean operators in the beginning of a line I often consider which style should I use A or B?


When I write boolean operators at the end it's almost no question.

Second is alignment of from/order by/group by/index keywords. I prefer indenting them twice for a better readability. But this can be questioned.

Use proper indentation

I follow these guidelines:
  • Indent once where and setting keywords;
  • Do not indent joins. Put them at the same level as first statement (see very last example);
  • Indent group by/order by/index and from (if its moved to a new line) at least once.
    I prefer indenting then twice.
Keep field list compact

Follow the rule: if a field list contains more than 5 fields or it exceeds 100 chars, break it down and use column alignment. Use one column then breaking down.
This rule applies for field list, order by, group by and setting field list.

Make where clause look nice

This one is not an easy task. Especially, then it is a very complex where clause with lots of ANDs an ORs.

Follow these rules:
  • One comparison per line except if it's very short.
  • Then using clauses indent expressions inside by one space except the first one. Keep the first expression in the same line as opening clause "(". Closing clause ")" should be placed at the end of the last expression inside the clause.
  • Rearrange expressions to make it more readable. Try to keep the similar expression close to each other. 
  • Then using NOT operator "!" treat is as part of expression. Don't use special alignment.
  • Then writing AND and OR at the end make them leveled as clauses so the structure can be easily recognized.
Consider this example. Does this look nice?
It's code from standard class BatchRun method serverProcessFinishedJobs

Does this look more readable?

Or maybe this?

You are more than welcome to share your experiences and best practices. Please give a proper reason why you are doing it one or another way.

Be aware and take care!

2015 m. gegužės 15 d., penktadienis

AX Source Code Quality: What is a Bad Source Code?

Hello AX World,

During my short career I have had a chance to see lots (50+) of different AX implementations and solutions from various countries. I have seen both good and bad code. Unfortunately, in most of the cases it was a bad code. And its a problem, because a person who is reading a bad source code can get frustrated quickly.

But what is a bad code? Why it's bad? How to make it better? To answer these questions and to help you to write a better code I am going to start a series of blog post regarding AX source code quality. I am also open for discussions as there are many arguable topics (e.g. code commenting).

I will start my first post with an example to consider:

Keep in mind, this is an overridden method in a class extending RunBase class (from RunBase framework. Ignore the fact that RunBase is an obsolete framework in AX 2012.

How many bad practices can you notice? Before reading it further try to test yourself.
So, how many issues you have noticed? 1, 2, 3,... more?

Let's try with some bad practice basics.

1. Application elements should be started with a capital letter to easily distinguish them among variables.
2. Brackets missing around if statement even if its a single line for a better readability.
3. There should be a single space between addition operators.
4. There should be a single space after a separator.
5. Redundant empty line.

Got the idea? Now lets move on with more sophisticated issues.

6.There should be a single label and preferably strfmt function be used instead  of two labels and a number of adding operators.

7. Instead of calling infolog.add method, error method could be called as it contains the same code and requires only one parameter, the text.

8. Validation logic should be moved to a validate method instead. The method getFromDialog is used to parse values from the dialog after it has been closed by clicking OK. This method has the code which calls validate method afterwards.
9. Validation code could be optimized. Well, this can be arguable.
So I have found 9 problems. Is that a lot for 20 lines of code? Maybe an eagle-eye professional could add even more. All thoughts are welcome.

So to finalize the first post I strongly believe that the code should look like this:

If you can see any issues with my suggested code, feel free to comment.

Be aware and take care!

2015 m. gegužės 14 d., ketvirtadienis

When Collation Takes Over

Hello AX World,

I once had a very interesting issue worth to share. I have recalled it recently when I wrote a SQL script.
This is specifically for databases with the collation Danish_Norwegian_CI_AS.
Consider these two statements:
The first is valid and the second is not. Why? Because AA and aA is treated differently in this collation.

I was running an upgrade (AX 2009 to AX 2012 CU7) and it failed on source environment, live preprocessing stage. It failed on the script:

After spending some time and analyzing it turned out that the issues was due to the DB collation.

The upgrade framework have registered the script as "smAAgreementLinePreUpgradeProcess" but later it searched for "smaAgreementLinePreUpgradeProcess".
See the difference. AX is case insensitive! How could it happen?

As mentioned earlier, AA and aA is treated differently in in Danish_Norwegian_CI_AS collation. AA is converted to a Danish/Norwegian letteÅ and aA is remains aA, therefore the SQL statement could not find what it searched for.

The issues was resolved by making the registered value and search value equal. 

In particular, I had to change one line in method ReleaseUpdateTransformDB50_SMA.initTransformationJobs when it registered the script.

This a good example that a great developer have to consider much more than just it's code.

Be aware & take care! :)

2015 m. vasario 12 d., ketvirtadienis

Valid Time State Key Error after Upgrading or Updating

Hello AX World,

Have you ever had a compilation error “Table does not contain a valid time state key. A unique index needs to be marked as a valid time state key.” and could not realize where it came from? I had… many times and I did not have a clue until one day.

Such errors look strange as they show up on completely standard objects.

I have noticed that they were showing up after upgrades or updates (CUs, hot fixes etc.). That gave me a hint. I know that during an upgrade there are upgrade scripts running to enable or disable certain indexes. So I assumed that these scripts are not behaving well enough. I tried to dig it up, to find scripts that were failing and I have found the problem.


The the index is disabled using indexAllowDup method, then properties AlternateKey and ValidTimeStateKey are reset to default value No.

ReleaseUpdateDB::indexAllowDup(new DictIndex(tableNum(TableName),
indexNum(TableName, DateEffectiveIndex)));

The error starts to show up then indexes are not enabled correctly using method indexAllowNoDup:

ReleaseUpdateDB::indexAllowNoDup(new DictIndex(tableNum(TableName),
indexNum(TableName, DateEffectiveIndex)));

AlternateKey and ValidTimeStateKey properties are not set using the code and then you start getting compilation error mentioned above.

You should use the method indexAllowNoDupAndDateEffective instead:

ReleaseUpdateDB::indexAllowNoDupAndDateEffective(new DictIndex(tableNum(TableName),
indexNum(TableName, DateEffectiveIndex)), ValidTimeStateMode::Gap or NoGap);

Sometimes you can see the following code, but that is implemented in indexAllowNoDupAndDateEffective method:

DictIndex dictIndex = new DictIndex(tableNum(TableName),
        indexNum(TableName, DateEffectiveIndex));
    dictIndex.modify(true, false, true);
    dictIndex.setAlternateKey(true, true);
    dictIndex.setValidTimeStateKey(true, ValidTimeStateMode::Gap or NoGap, true);
    appl.dbSynchronize(dictIndex.tableid(), false);

Recent experience

I have got 3 errors after upgrading from AX 2009 to AX 2012 R3. In earlier versions I had experienced even more…

Tables that there failing:
  1. EximDBKValues_IN (Index: TariffCodeIdx)
  2. EximDEPBScheduleTable_IN (Index: ProductGroupTableRecIdx)
  3. PdsRebateAgreement (Index: SeqIdx)
EximDBKValues_IN table had 4 (!) methods disabling/enabling the same index. The problem was that the incorrect script have been executed the last thus "enabled" the index incorrectly.

  1. ReleaseUpdateDB60_Cust. allowDupEximDBKValues_IN
  2. ReleaseUpdateDB60_Cust. allowNoDupEximDBKValues_IN
  3. ReleaseUpdateDB60_Ledger.allowDupEximDBKValues_INTariffCodeIdx
  4. ReleaseUpdateDB60_Ledger.allowNoDupEximDBKValues_INTariffCodeIdx
EximDEPBScheduleTable_IN table had 2 methods as expected but index enabling was incorrect.
  1. ReleaseUpdateDB60_Ledger.allowDupEximDEPBScheduleTable_INProductG
  2. ReleaseUpdateDB60_Ledger.allowNoDupEximDEPBScheduleTable_INProduc
public void allowNoDupEximDEPBScheduleTable_INProduc()
    ReleaseUpdateDB::indexAllowNoDup(new DictIndex(tableNum(EximDEPBScheduleTable_IN),
        indexNum(EximDEPBScheduleTable_IN, ProductGroupTableRecIdx)));

PdsRebateAgreement table had 2 methods, the same as EximDEPBScheduleTable_IN and enabling index was incorrect.
  1. PmfReleaseUpdateDB60_Pds.allowDupPdsRebateAgreementSeqIdx
  2. PmfReleaseUpdateDB60_Pds.allowNoDupPdsRebateAgreementSeqIdx
public void allowNoDupPdsRebateAgreementSeqIdx()
    ReleaseUpdateDB::indexAllowNoDup(new DictIndex(tableNum(PdsRebateAgreement),
        indexNum(PdsRebateAgreement, PdsRebateAgreementSeqIdx)));

I hope that helps you to save some valuable time to spend on more interesting things.


2015 m. sausio 16 d., penktadienis

Upgrading Number Sequences to AX 2012 That Does Not Mach The Format

Hello AX world,

This time I would like to share some of my upgrade experience. Specifically, how to deal with the issue when a number sequence does not mach the format.
This applies to upgrades from AX 2009 (and earlier) to AX 2012.

Scenario: A number sequence's format has been changed over time. Let's say you had a format XXX-###### (6#) and you had ran out of numbers reaching XXX-999999 number. After that you changed the format to XXX-######## (8#).

That is all fine until you need to upgrade it. During the upgrade the script called 'Transfer number sequence framework tables. ' will fail saying that the number N does not mach format X
Script class: ReleaseUpdateDB60_Basic; method: updateNumberSequenceFrameworkPostSync.

The problem occurs when it tries to upgrade records from DEL_NumberSequenceList to NumberSequenceList table. It tries to calculate NextRec from the number sequence number. To get the number it calls NumberSeq::numRemoveFormat() method which throws an error then the format does not match the number.
Interesting to note that two different numbers XXX-123456 and ZZZ-123456 would mach the format XXX-###### but the table NumberSequenceList does not allow duplicated entries for fields: NumberSequenceIdStatus and NextRec due to unique index NumIdx.
So we have two problems, one when the format does not mach and another when duplicated numbers exist.

I would not say that the workaround I am going to explain here it's the best solution to the problem but it had helped me a lot in several upgrades and it did not result in any problems (at least I was not informed of any).

My approach to the problem was making sure that NextRec does not duplicate.

Firstly, I needed to bypass the error of the method that tries to generate me a number, NumberSeq::numRemoveFormat(). I did it by creating my own method numRemoveFormatNoError(). Instead of throwing an error it returned 0, meaning that the number was not generated.

Secondly, I needed to change the logic of upgrading DEL_NumberSequenceList records to NumberSequenceList.

I managed to do it by splitting the process into tree steps:
  1. Create records for numbers that mach the format and collect duplicated number sequence numbers.
  2. Create records for numbers that are duplicated. NextRec is assigned to be lastNextRec + 1.
  3. Created records for numbers that do not match the format. NextRec is assigned to be lastNextRec + 1.
Using this technique I have solved the problem for both invalid format  and duplicated numbers. And the records were, I would call semi-consistent.

I hope this article helps you to save some time during the upgrade.

P.S. If you would like to receive the code just drop me a comment with you email. I have not found if I can upload an xpo file here.

Evaldas Landauskas