Exception Handling in Dynamics AX
Filed under: #daxmusings #bizapps
Exception handling in Dynamics AX is a topic that is not discussed too often. I figured I would provide a quick musing about some of my favorite exception handling topics.
Database Transactions Exception handling while working with database transactions is different. Unfortunately, not a lot of people realize this. Most exceptions cannot be caught within a transaction scope. If you have a try-catch block within the ttsBegin/ttsCommit scope, your catch will not be used for most types of exceptions thrown. What does happen is that AX will automatically cause a ttsAbort() call and then look for a catch block outside of the transaction scope and execute that if there is one. There are however two exception types you CAN catch inside of a transaction scope, namely Update Conflict and Duplicate Key (so don’t believe what this MSDN article says). The reason is that it allows you to fix the data issue and retry the operation. You see this pattern in AX every now and then, you have a maximum retry number for these exceptions, after which you throw the “Not Recovered” version of the exception. The job below shows a generic X++ script that loops through each exception type (defined by the Exception enumeration), throws it, and tries to catch it inside a transaction scope. The output shows if the exception is caught inside or outside the transaction scope.
<pre>static void ExceptionTest(Args _args)
{
Exception exception;
DictEnum dictEnum;
int enumIndex;
dictEnum = new DictEnum(enumNum(Exception));
for (enumIndex=0; enumIndex < dictEnum.values(); enumIndex++)
{
exception = dictEnum.index2Value(enumIndex);
try
{
ttsBegin;
try
{
throw exception;
}
catch
{
info(strFmt("%1: Inside", exception));
}
ttsCommit;
}
catch
{
info(strFmt("%1: Outside", exception));
}
} }
</pre></code>
Fall Through
Sometimes you just want to catch the exception but not do anything. However, an empty catch block will result in a compiler warning (which of course we all strive to avoid!). No worries, you can put the following statement inside your catch block:
Global::exceptionTextFallThrough()
Of course, you’re assuming the exception that was thrown already provided an infolog message of some sort. Nothing worse than an error without an error message.
.NET Interop Exceptions
When a .NET exception is thrown, they are typically “raw” exceptions compared to our typical ‘throw error(“message here”)’ informative exceptions. I’ve seen quite a lot of interop code that does not even try to catch .NET call exceptions, let alone handle them. The following examples show different tactics to show the actual .NET exception message. Note that not catching the error (trying to parse “ABCD” into an integer number) does not result in ANY error, meaning a user wouldn’t even know any error happened at all.
Strategy 1: Get the inner-most exception and show its message:<pre>static void InteropException(Args _args)
{
System.Exception interopException;
try
{
System.Int16::Parse("abcd");
}
catch(Exception::CLRError)
{
interopException = CLRInterop::getLastException();
while (!CLRInterop::isNull(interopException.get_InnerException()))
{
interopException = interopException.get_InnerException();
}
error(CLRInterop::getAnyTypeForObject(interopException.get_Message()));
} }
</pre></code>
Strategy 2: Use ToString() on the exception which will show the full stack trace and inner exception messages:<pre>static void InteropException(Args _args)
{
System.Exception interopException;
try
{
System.Int16::Parse("abcd");
}
catch(Exception::CLRError)
{
interopException = CLRInterop::getLastException();
error(CLRInterop::getAnyTypeForObject(interopException.ToString()));
} }
</pre></code>
Strategy 3: Get all fancy and catch on the type of .NET exception (in this case I get the inner-most exception as we previously have done). Honestly I’ve never used this, but it could be useful I guess…<pre>static void InteropException(Args _args)
{
System.Exception interopException;
System.Type exceptionType;
try
{
System.Int16::Parse("abcd");
}
catch(Exception::CLRError)
{
interopException = CLRInterop::getLastException();
while (!CLRInterop::isNull(interopException.get_InnerException()))
{
interopException = interopException.get_InnerException();
}
exceptionType = interopException.GetType();
switch(CLRInterop::getAnyTypeForObject(exceptionType.get_FullName()))
{
case 'System.FormatException':
error("bad format");
break;
default:
error("some other error");
break;
}
} }
</pre></code>
throw Exception::Timeout;
There is no comment section here, but I would love to hear your thoughts! Get in touch!
Blog Links
Blog Post Collections
- The LLM Blogs
- Dynamics 365 (AX7) Dev Resources
- Dynamics AX 2012 Dev Resources
- Dynamics AX 2012 ALM/TFS
Recent Posts
-
GPT4-o1 Test Results
Read more... -
Small Language Models
Read more... -
Orchestration and Function Calling
Read more... -
From Text Prediction to Action
Read more... -
The Killer App
Read more...