Posts RSS Comments RSS 24 Posts and 45 Comments till now

A glimpse of version control options for Ax 5.0

Michael Fruergaard Pontoppidan created a screencast about the version control options for the next major Ax release. In addition to Visual Sourcesafe, Ax will also support Team Foundation Server and a new source control system in Ax itself.

It looks promising but so was the prospect of Sourcesafe integration for 4.0. Unfortunately it turned out to be only marginally usable. The requirement to have a complete database and AOS setup per developer makes it hard to handle multiple projects or do development at the customer’s site.

The video seems to acknowledge this. Sourcesafe and TFS are clearly positioned as tools for ISVs that make modules. Developing modules is different than doing a customer specific implementation and can more easily incorporate VSS or TFS as part of their toolbox. Although I’m skeptical about this, I really hope the new Morphx VCS will provide a decent source control solution for all of us. Ax needs more tools like that to make good development practices less cumbersome.

Installing Dynamics Ax 4.0 SP1 and SP2 side by side

When a new service pack is released I like to install it next to all the other versions I have on my machine. This makes it easy to test new service packs and compare it to previous versions. In 3.0 this was quite easy to do because you could choose which directory you wanted to upgrade. The new installation procedure and requirements for version 4.0 makes this a lot harder. Fortunately it’s not entirely impossible.

The installer doesn’t give you much choice about what to upgrade, so you’ll have to copy files and modify registry keys manually. Below are the steps to make it work on my machine. Beware that this setup is not something you should do on a production environment because it’s not supported by MBS. Don’t try this if you don’t feel comfortable editing the Windows registry.

I’m starting with SP1 because I don’t have to support environments without a service pack. This procedure probably works for a regular 4.0 as well. Everything is installed on a single machine, starting from scratch.

  1. Install SP1 database, client, server, and application. I used Client, Application, and Server. Pick a good name for the AOS (I used DAX_401). Don’t start it yet.
  2. Copy the three directories. I used Client SP1, Application SP1, and Server SP1. You should have 6 directories now.
  3. Modify permissions on the SP1 directories to give the account the AOS uses all permissions except for Full Access and Special Permissions. The default account is Network Service.
  4. Delete the DAX_401 AOS files and install a second SP1 AOS in the Server directory. You need to do this to get the necessary registry entries. I named the instance DAX_402.
  5. Install a second SP1 database or copy the one created during the first installation.
  6. Install SP2. This will upgrade the everything created in step 1.
  7. With the config utilities, create server and client configurations. At this point the SP2 part should work.
  8. Open Regedit and search for the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AOS$01. This is the service information SP1 AOS. Change the ImagePath key to point to the executable in Server SP1. In the Management Console for Windows services the path to the executable for AOS01 should point to the SP1 directory.
  9. In Regedit, go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dynamics Server\4.0\01\. Below this are the server configurations. Several keys have a path that point to the SP2 directories for application and server. Change everything to the SP1 directory.
  10. The SP1 AOS should now work.
  11. Optionally, create shortcuts to the SP1 and SP2 clients. The SP2 client is backward compatible. It can connect to an SP1 AOS but it the SP2 AOS won’t accept SP1 clients.

I don’t know if it’s possible to have 2 versions of the Business connector. I upgraded mine to SP2. Like the client it will probably be able to connect to an SP1 application.

If you’re not interested in using the SP1 binaries you can probably copy an SP1 application to the SP2 Appl directory and configure the AOS to use the old application. I haven’t tested it yet so I don’t know if it actually works.

The lone semicolon

Something that confuses and annoys people when they’re learning X++, is the semicolon after variable declarations.

As you know all local variables have to be declared at the start of the function and statements are placed after that. Then comes the important part: don’t forget to always put a semicolon on a single line before the first statement.

If you don’t, your code might not compile. Sometimes it will, sometimes it won’t. And code that works now can be broken later, without touching it. A semicolon is not always required, but instead of worrying if it’s required, it’s easier to make a habit of just writing it. Even if you don’t have any local variables. Unfortunately even standard X++ code doesn’t always have this.

The reason you need that extra semicolon is because the compiler can’t always see where the variable declarations end. If you don’t help a little, it will make a guess. And it’s not very good at guessing.

While the compiler is analyzing the code it checks if the first word on a line matches the name of a type (AOT object). If it’s a type name the compiler treats the line as a variable declaration. In this case a variable name should be next.

If the first word is not a variable the compiler considers the line to be the start of a statement. In some cases it treats statements as class declarations and that causes compiler errors.

Let me explain this with some examples from the standard application (4.0 SP1). This is a method from the BOMSearch class.

1
2
3
4
5
6
7
8
9
10
11
12
13
server static BOMSearch newProdTable(ProdTable prodTable)
{
    BOMSearch BOMSearch = new BOMSearch(
                            prodTable.BOMId,
                            prodTable.BOMDate,
                            prodTable.ItemId,
                            prodTable.inventDim().ConfigId,
                            false);
    ;
    BOMSearch.init();
 
    return BOMSearch;
}

If you remove the semicolon from line 9 you will get an error on the next line. The first line is never a problem for the compiler: a variable BOMSearch of type BOMSearch is created.

Next the compiler sees the word BOMSearch and figures, ‘I know that, it’s a class name. So this must be a variable declaration. But what’s with the .init()? That’s not allowed here.’

And there’s the compilation error.

This method from CustTable is an example of bad standard code.

1
2
3
4
5
void initFromCustGroup(CustGroup _custGroup)
{
    this.PaymTermId = _custGroup.PaymTermId;
    this.PaymSched  = PaymTerm::find(this.PaymTermId).PaymSched;
}

‘What do you mean, bad code? This is standard Axapta. It works just fine!’.
Yes it does… for now. Create an EDT named this and compile again. It’s broken for the same reason as the previous example.

To avoid headaches, repeat after me: always put a semicolon on a single line before the first statement.

Too bad the compiler isn’t smarter. A Best Practice check would be nice too.

The X++ way to getters and setters

This one is for all those who are new to X++ development. When learning OO programming we’re told that encapsulation is important. Data members (variables) should be hidden and any access should go through methods. Then the typical combination of getters and setters is introduced. You’re supposed to write methods for each thing you want to expose: getSomething() and setSomething(someValue).

In X++ we do this too… with a twist. For starters, all data members of a class (variables in the ClassDeclaration) are protected. So only the class itself and subclasses can access them directly. This means X++ forces you to write methods to access data from outside the class hierarchy. Unlike other mainstream languages such as Java, C# or C++ there is nothing you can do about it. Keywords such as private, protected or public are simply not allowed in a ClassDeclaration.

And there’s more, there are no getters and setters either. It’s not that writing getters and setters is impossible, it’s just not the way things are done. Everything is rolled into a single method, with a name starting with parm. Sometimes they’re called property methods or parm-methods.

A typical example looks like this:

CustAccount parmCustAccount(CustAccount _custAccount = custAccount)
{
    ;
    custAccount = _custAccount;
    return custAccount;
}

It may look confusing but it is actually quite simple. If you use it as a setter, the new value is passed to the object’s data member (custAccount) and the return value is never used. When used as a getter, _custAccount gets the current data member as a default value and this value is eventually returned.

The important thing is that you can use it just like getters and setters you might know. Instead of getCustAccount(), use parmCustAccount(). Instead of setCustAccount(‘1234′), use parmCustAccount(‘1234′).

I recommend you do it this way for any new classes you create. This is something I need to point out in code reviews with new X++ developers all the time. You could argue that the function does two things and in theory it should only do one thing. That’s true. However, this is a minor offense that will not make or break your application. Adhering to the existing code style is important too. Being consistent improves overall readability and usability of the code. When in Rome do as the Romans do.

A quick tip for enums

When you’re creating a new base enum consider using multiples of 10 for values. This makes extending enums a lot easier, especially if it indicates some kind of status. Those of you who once programmed in BASIC, or any other language with line numbers, can probably guess where I’m going with this. My apologies for any painful flashbacks this may trigger.

Suppose you’re creating an interface with another system. It turns out you need a new enum, MessageStatus. Values are assigned automatically, so Received = 0, Started = 1, Processed = 2, and Archived = 3.

Of course, at some point you need to do different things depending on the status. Let’s say you have written this function.

boolean isOpen(MessageStatus _status)
{
    return _status < MessageStatus::Processed;
}

This is a simple way to indicate at which point the message is no longer considered open. Standard Dynamics Ax contains similar examples, e.g. with the inventory status (StatusIssue and StatusReceipt).

Your message handling system goes live and works perfectly. Users are happy, birds are singing, the sun is shining.

After a while users decide they need a verification step before moving to Started. You extend the enum and end up with MessageStatus::Verified = 4. And now things are broken. Verified messages aren’t handled correctly.

You need to check all places where the status is used. You run across the isOpen() method and change it.

boolean isOpen(MessageStatus _status)
{
    return _status < MessageStatus::Processed || _status == MessageStatus::Verified;
}

Not so readable anymore but it works. For now. Things can get out of hand quickly if more than 1 status is added.

This could have been avoided from the start if the enum elements were assigned specific values.

Received 10
Started 20
Processed 30
Archived 40

Adding a new step somewhere in the middle is easy. Create Verified with value 15 and isOpen() doesn’t need to be modified.

Now you may think this is a great idea (It is. Thank you.) but don’t go changing all your enums now. Changing values means you also need to modify all fields that use it, as they still contain the old numbers. This is the reason you need to think about enum values in advance. Once a system is deployed, it’s usually better to leave these things as they are to avoid even more trouble.

In practice starting with multiples of 10 is enough to deal with any reasonable modifications. I have yet to run into a situation where a gap of 9 values isn’t enough. Unfortunately standard Dynamics Ax enums have continuous values starting at 0. If you need to insert something in between you still have to check all code that covers a range of enum elements.

Inventory marking issue in Axapta 3.0

There’s a problem when modifying marked transactions. It’s possible to end up with a transaction that is not reserved but has a reference lot ID anyway, i.e. is marked to another transaction. This can lead to hard to explain problems down the line, e.g. after running the master planning planned orders are missing or incorrectly matched to outgoing transactions.

Scenario

There is an easy way to trigger this. Create a sales order for an item. Then create a linked purchase order for the amount sold. This marks both inventory transactions to each other. The inventory transaction for the sales line issue is in status reserved ordered and contains the reference lot ID of the purchase order line.

Now increase the quantity on the sales line. This creates a second inventory transaction for the new quantity in issue status on order and with the same reference lot ID. This means the new quantity is also marked to the same purchase order. This is obviously not correct.

Marking implies reserved transactions (physically or ordered) and the quantities on both sides should match. In this case the purchase order is not sufficient for the quantity sold. That’s why the new transaction is put in status on order in the first place. Unfortunately the marking is not fixed.

Something similar happens when you increase the purchase quantity instead of the sales quantity.

Fix

This behavior is fixed in Dynamics Ax 4.0 SP1. And the good news is it can be backported. The bug resides in InventUpd_Estimated.createEstimatedInventTrans(). This is where a new InventTrans record is created for the added quantity. It correctly determines the issue status and then stops. In 4.0 the marking is checked there as well.

The end of the method looks like this:

63
64
65
66
if (movement_Orig)
    inventTrans.updateSumUp();
 
updEstimated += qty;

Replace it with this:

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
updEstimated += qty;
 
// Fix 4.0 SP1 >>
if (movement.inventRefTransId()) // Marking for entire lotId exists => additional should also be marked
{
    markNow = InventTrans::updateMarking(movement.inventRefTransId(), movement.transId(), -qty,  '', SortOrder::Descending);
 
    if (markNow)
    {
        if(abs(markNow) < abs(inventTrans.Qty))
            inventTrans.updateSplit(inventTrans.Qty > 0 ? abs(markNow) : - abs(markNow));
 
        inventTrans.InventRefTransId =  movement.inventRefTransId();
        inventTrans.update();
 
        InventTrans::findTransId(inventTrans.InventRefTransId, true).updateSumUp();
    }
    if (qty < 0) // issue
    {
        InventUpd_Reservation::updateReserveRefTransId(movement);   // try to make reservation according to marking -
    }
    else
    {
        inventTransMovement = InventTrans::findTransId(movement.inventRefTransId());
 
        if(inventTransMovement)
        {
            movementIssue = inventTransMovement.inventMovement(true);  // no Throw if not initiated
 
            if(movementIssue)
                InventUpd_Reservation::updateReserveRefTransId(movementIssue);  // try to make reservation according to marking -
        }
    }
    if (!markNow && inventTrans.InventRefTransId) // reset InventRefTransId if no marking could be made
    {
        inventTrans.InventRefTransId = '';
        inventTrans.update();
    }
}
// <<
 
if (movement_Orig)
    inventTrans.updateSumUp();

You’ll also need to add these variables at the top:

InventQty           markNow;
InventMovement      movementIssue;
InventTrans         inventTransMovement;

Final remarks

I could reproduce this on a standard 3.0 SP5 installation. I was a bit surprised to see that this has gone unnoticed for so long. However it is a subtle bug that is hard to spot. The transaction status is correct and the effects of marking aren’t immediately visible.

This kind of inconsistency can really mess up the InventTrans table. Without the fix try, increasing the purchase quantity and then on the sales order remove the inventory marking completely. You end up with several inventory transactions for the purchase order. Some of which are still marked to the sales order.

X++ syntax highlighting

This is fun. With this new blog setup, syntax highlighting for X++ is available.

while select custTable
{
    print custTable.Name;
}
pause;

Thanks to Simon Butcher for making the X++ syntax file for Geshi.

Welcome to the new site

Glad to see you here.

The old posts are back up and running now. Links and images should be fixed after the move.
Stay tuned for some real content about Dynamics. I’ll be tweaking the site as I go along.

I hope you like the new layout.

10 tips for debugging in Dynamics Ax

Fixing bugs requires quite a bit of experience and knowledge of the modules involved, both on a technical and functional level. The first step to fix something is to find the cause of the problem, a.k.a. debugging.

You shouldn’t limit yourself to using the debugger when things go wrong. Debugging can help you understand the system. I often fire up the debugger just to see what happens in a standard application. This helps me to see how modifications can be implemented and what the consequences are. Dynamics is too big and too complex to be able to just dive in and change something.

Here are some tips to help you in the fine art of debugging. Some might be blatantly obvious to experienced developers. These are things I wish I had known when I first started working with Axapta.

Assume you broke it
This is probably the most important advice. We developers tend to think we write good code. Some of us do, some of us don’t. But nobody does it flawlessly. By default, assume anything you didn’t write yourself works perfectly. This narrows down the search considerably. After careful debugging you may come to a different conclusion. In which case you’ll have a good bug report to file.

If a system has been running fine for a while and it suddenly breaks down after importing new code, those changes are likely to be the root cause of the problem. Try reverting your changes and doing the exact same thing. If the problem remains, you have found an unrelated problem. If not, you know where to start looking for errors.

Get a clear description of the problem
Unless the error is clear enough and you immediately know how to fix it, you’ll need a detailed description how to trigger this error. Unfortunately this can be very hard. Getting users to tell you exactly what you need to understand a bug isn’t that simple. Keep in mind that users are generally not interested in the program they’re using. They just want to get their job done. They have been taught to use the system in a certain way and unexpected errors confuse them. They might not realize what’s different when things go wrong compared to when everything just works.

You need to ask the right questions. If necessary sit next to them and watch them work. Take notes and try to notice special cases. And don’t forget to ask what the correct behaviour should be. There may be no error message and whatever happens may look correct but the user could be expecting a different result.

Without a good scenario it may be impossible to solve some bugs.

Don’t worry to much about errors that only occur once
If something goes wrong only once and it doesn’t happen again, don’t worry too much about it. Depending on the risk it may be better to fix the damage and move on. There’s probably a bug lurking somewhere but you have to decide if it’s worth chasing it.

Intercept error messages
Anything sent to the info log window passes through the add() method on the Info class. Put a breakpoint there if you want to know where a message is triggered. Using the stack trace in the debugger it’s usually not that hard to see which conditions cause it.

Often it turns out to be a missing setting in one of the basic tables.

Intercept data modifications
Not all bugs come with an easy to intercept error message. Sometimes all you get is bad data. It’s possible to see when and why records are created, modified or deleted by putting breakpoints in insert(), update() or delete() on a table. Create them if necessary. Just being able to look at the stack in the debugger when these are called can be very insightful.

Remember that it is possible to modify data without passing through these methods. Like using doInsert(), doUpdate() or doDelete(), or using direct SQL. It’s not very common but sometimes you can miss something.

Intercept queries
If you suspect a query is not correct you’ll want to verify its output. A way that doesn’t require much work is using the postLoad() method. It can be overridden on each table and is called for each selected record. It even works with complex joins. Putting an info() in the postLoad() of each table in a query can tell you a lot about what’s happening.

The cross-reference is your friend
The cross-reference is one of the most important tools when developing and debugging in Dynamics Ax. Always try to have an environment somewhere with an updated cross-reference (not the live environment). You can find the cross-reference in the development tools menu.

Need to know where a field gets its value? The cross-reference tells you where every read and write happens.
Want to know where an error message is used? Open the label editor and find the label, then click the Used By button.

Set up a separate environment
When dealing with complex problems it helps to have a separate environment for debugging. This allows you to freely modify code and data without affecting the live system. This is very important when you have to post invoices or do anything else that is basically irreversible.

It also prevents live users from being blocked if you have breakpoints in the middle of a transaction.

Dealing with large datasets
Sometimes a problem can only be reproduced in (a copy of) the live environment. You’re often stuck with a lot of data that doesn’t matter but gets in the way. Like when you need to debug the MRP. Using regular breakpoints doesn’t help because it takes too long before you get to the real issue.

In this case you need to have some more tricks up your sleeve to narrow down the search. One option is to work in several passes. Using the cross-reference determine places where something interesting happens and dump data with info() or Debug::printDebug(). This should narrow down the possible suspects. With a bit of luck just looking at the data can be enough to identify the problem.

Another way is implementing your own conditional breakpoints. The debugger doesn’t offer these out of the box but you can roll your own with an if-statement and the breakpoint statement. This is very effective if you have some more or less unique identifier of the problem, like a record ID or a customer account or even a date.

Clean up
Don’t forget to remove any modifications you made while debugging. You probably don’t want to leave a hardcoded breakpoint in a live system. Been there, done that, very annoying.

Good luck hunting for bugs.

Feel free to share your debugging techniques.

Reflection with the Dictionary

In X++ it is possible to write code that inspects the structure of other X++ code. This is sometimes called reflection or meta-programming.

You may wonder why you’d want to do that. Reflection is a very powerful tool and opens a wide array of possibilities. Without it some things would be very hard or even impossible to implement. Just look at the code behind table browser, the unit testing framework, or the data export and import tools.

There are several ways to do reflection in X++. I’m going to show an example using the Dictionary. It involves classes whose names start with Dict and SysDict. The latter are subclasses of their respective Dict-class and can be found in the AOT.

The goal

Suppose you need to analyze the performance of an existing application. You could set up monitoring but you need an indication where to start. The largest tables in the database, i.e. those with most records, are potential performance bottlenecks. For large tables it is important to have the right indexes that match the usage pattern of the customer. We’re going to make a simple tool to find those tables. You could also check if new tables from customizations have indexes and so on.

Getting started

First we need to get a list of tables in the application. The kernel class Dictionary has the information we need. It tells us which classes, tables and other objects are defined in the application. To iterate over the list of tables we can use something like this:


static void findLargeTables(Args _args)
{
    Dictionary      dictionary;
    TableId         tableId;
    ;

    dictionary = new Dictionary();

    tableId = dictionary.tableNext(0);

    while (tableId)
    {
        info(int2str(tableId));

        tableId = dictionary.tableNext(tableId);
    }
}

The tableNext() method gives the ID of the table following the given ID. So we start with the non-existant table ID 0 and get back the first table in the system. For now we’ll just print the result to the infolog.

Weeding out the junk

If you scroll through the infolog you’ll notice it also includes things we aren’t interested in, such as temporary tables, (hidden) system tables, views, and table maps. We need to skip these.

Enter the SysDictTable class. Whenever possible you should use the SysDict version of any class in the Dictionary API because they contain very useful additional methods. You’ll see an example in a minute.


static void findLargeTables(Args _args)
{
    Dictionary      dictionary;
    TableId         tableId;

    SysDictTable    dictTable;
    ;

    dictionary = new Dictionary();

    tableId = dictionary.tableNext(0);

    while (tableId)
    {  
        dictTable = new SysDictTable(tableId);

        if (!dictTable.isMap() && !dictTable.isView() &&
            !dictTable.isSystemTable() && dictTable.dataPrCompany())
        {
            info(strFmt('%1 - %2', tableId, tableId2Name(tableId)));
        }

        tableId = dictionary.tableNext(tableId);
    }
}

Some methods tell us what kind of table we’re dealing with and any special case is ignored. For this example I’m only interested in a single company.

Counting

Now need to know which tables have the most records. SysDictTable can count the records for us. To keep track of the results we’ll use an array. The index indicates the number of records and the value is a container of table names. This is a simple data structure that doesn’t require any new tables or classes. The results are ordered and it can deal with several tables having the same record count. The only catch is we need to keep in mind that not all array indexes will have a value.

It’s easier than it sounds. First we take out the info() in the loop and put in some real logic.


        if (!dictTable.isMap() && !dictTable.isView() &&
            !dictTable.isSystemTable() && dictTable.dataPrCompany())
        {
            currCount = dictTable.recordCount();
            if (currCount > 0)
            {
                if (recordCounts.exists(currCount))
                {
                    tables  = recordCounts.value(currCount);
                    tables += dictTable.name();
                }
                else
                {
                    tables = [dictTable.name()];
                }

                recordCounts.value(currCount, tables);
            }
        }

We ignore empty tables and then check if we need to add our table to an existing container or create a new one.

After inspecting the tables we can print the top 10.


    printed = 0;
    i = recordCounts.lastIndex();
    while (i > 0 && printed  0 )
    {
        if (recordCounts.exists(i)
          && conLen(recordCounts.value(i)) > 0)
        {
            info(strFmt("%1 - %2", i, con2str(recordCounts.value(i))));
            ++printed;
        }
        --i;
    }

What’s next?

To make it more useful you could add more checks. I included some of these in the XPO.

  • cacheLookup() : to check if a good cache level is set.
  • indexCnt(), clusterIndex() and primaryIndex() : if you want to know if the table has indexes. For large tables a good set of indexes can make a big difference.
  • tableGroup() : for filtering out transaction tables, which are often the ones that need most tuning. Or to find all those Miscellaneous tables that should be in another group.
  • fieldCnt() : counts the number of fields. Tables with a lot of fields take up more space and require more round trips between AOS and database when fetching data. So don’t go overboard when adding new fields. It’s a good idea to check the field count every now and then when developing.
  • recordSize() : tells you how big a single record is in bytes. This depends on the number of fields and the data types.

There’s a lot more you can do with the Dictionary classes. To get an idea of the possibilities you can check how dictionary classes are used in the standard application.

In later posts I’ll give more examples how to read (and modify) the AOT structure in X++.
XPO for Dynamics Ax 4.0.

« Previous PageNext Page »