Tuesday, November 18, 2008

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

The mystery of "index" vs. "index hint"

In the Axapta community, there is still a big confusion about the "index" and "index hint" statements used in connection with selects.

So, what is the Axapta kernel *really* doing:

Using "index": when you add the statement "index MyIndex", the Axapta kernel will add an "ORDER BY" with all the fields of the index.

Example: select * from InventTable index GroupItemIdx will generate the following SQL statement to the database:

SELECT A.ITEMGROUPID, A.ITEMID, A.ITEMNAME,.... FROM INVENTTABLE A ORDER BY A.ITEMGROUPID, A.ITEMID

The Index ItemGroupIdx of the InventTable exactly contains the two fields ItemGroupID and ItemId (in that order). Using "index", you still give the control of which index to use to the database optimizer. So, if the optimizer finds a better index to use, it will use it.

Using "index hint": when you add the statement "index hint MyIndex", the Axapta kernel will add a statement to instruct the database to use that index and no other one.

Example: select * from InventTable index hint GroupItemIdx will generate the following SQL statement to the database:

SELECT /*+ INDEX(A I_175GROUPITEMIDX) */ A.ITEMGROUPID, A.ITEMID, A.ITEMNAME,.... FROM INVENTTABLE A

Using "index hint", you take away the control of which index to use from the database optimizer. So, if there may be a better index, the database will not use it.


Conclusion:

Adding the "index" statement to an Axapta select, it does NOT mean that this index will be used by the database. What it DOES mean is that Axapta will send an "order by" to the database.

Adding the "index hint" statement to an Axapta select, it DOES mean that this index will be used by the database (and no other one).

This rule applies to both the MSSQL and Oracle databases.

Where Breakpoints are stored for Axapta

If you ever wondered where Axapta stores the breakpoints, here is the answer:
breakpoints are stored in the registry under the key

HKEY_CURRENT_USER\Software\Navision\Axapta\3.0

Here's a screenshot:


Some facts about AOS clusters

For your information:
• When you have a SQL cluster for failover and the actual failover happens, the AOS will lose the connection to the SQL server and you have to restart the AOS service.

• When you have an AOS cluster and you set it to start "On demand", it will not work. If the AOS instances are not running they will never start when clients try to connect. Sad but true. Maybe this will change when we get the new Dynamics Ax 4.0.

Create a new method in runtime

Some one may need to use codes to create or edit a method in Axapta.
Here is an example to show how to create a lookup method for a form's field in runtime.



static void CreateFieldMethod(Args _args)
{
TreeNode tn1,tnAddr, methodsNode;
MemberFunction memberFunction;
str source;
;

//Thanks for Max Belugin's comments,
//it is really good to use Verbatim String as well.
//The reason why I use escape characters here is
//because this line of code is copied from standard Axapta application :)
tn1 = infolog.findNode("\\Forms\\Address\\Data Sources\\Address\\Fields\\AddrRecId");

tnAddr = infolog.findNode( "\\Forms\\Address" );
methodsNode = tn1.AOTfindChild( 'Methods' );
methodsNode.AOTadd('lookup');
memberFunction = methodsNode.AOTfindChild( 'lookup' );
source = @"public void lookup(FormControl _formControl, str _filterStr)
{
super(_formControl, _filterStr);
}" ;
memberFunction.AOTsetSource(source, false);
memberFunction.AOTsave();
methodsNode.AOTsave();
tnAddr.AOTcompile();
}

'Not Like' in Dynamics AX

In X++, we can use Like '*someIdentifier' to implement the Like keyword.
e.g.
select firstonly purchTable
where purchTable.purchId like '00007*';

However if you want to use 'Not Like' in X++ SQL statement, you have three options:
The first option, using '!' as 'not',
e.g.
select firstonly purchTable
where !(purchTable.purchId like '00007*');

The second option, using notExists join
e.g.
PurchTable purchTable, refPurchTable;
;

select firstonly purchTable
notExists join refPurchTable
where purchTable.purchId == '00007*';

Please make sure that you do put purchTable.purchId in condition statement, otherwise the SQL statement will retrieve an empty result set.
The last option, using Query
e.g.
Query query = new Query();
QueryRun queryRun;
;

query.addDataSource(tableNum(PurchTable)).addRange(fieldNum(PurchTable, PurchId)).value('!00007*');
queryRun = new QueryRun(query);
if(queryRun.next())
{
purchTable = queryRun.get(tableNum(PurchTable));
print purchTable.PurchId;
pause;
}

Bye bye, axdat.udb !!!

One of the very good things about Dynamics AX 4.0: there is no axdat.udb file anymore! All information about users online etc... is kept in the database.

The table that holds the users' data is called SYSCLIENTSESSIONS.
The table that holds the AOS' data is called SYSSERVERSESSIONS.

It seems to me that SysClientSessions will contain the sessions that had been logged in on the current day (no matter if they are still logged in or not). Currently logged in sessions will have the field STATUS set to 1.

DAX 4.0 Clustering question

"Wouldn't the session manager take care of directing a request to a certain
AOS ?
(I.e. load balancing).When not using the session manager, how is
determined which AOS in the cluster to use ?"

Well: I can only assume that it makes the same as Axapta 3.0 (but better): the client tries to connect to all AOS servers in it's configuration. Each AOS server tells the number of users connected, the client connects to the one with the lowest user count

In Ax 4.0 SP1, the new "session manager" for AOS clustering was introduced (so you do not need NLB anymore). This, however, means a new single point of failure, because if the session manager crashes, clients will not be able to login to AX anymore.
The good news, though, is that you do not need the session manager!

Make the following setup: several instances of AX, mark all with the flag "Make this AOS instance part of the load balancing cluster". In the client config, add all your AOS instances on the various servers. The client will connect to one of the available servers. And the good thing is: this setup will work, even if one or some of the AOS instances are not running (this did not work in Axapta 3.0: there the client would have said that it cannot connect to the AOS server).