Источник: http://dev.goshoom.net/en/2017/06/ca…tions-in-ax-7/
==============
In this blog post, I briefly recapitulate how to throw and catch exceptions in X++ and introduce a new way of handling CLR exceptions in AX 7.
X++ exceptions
When you want to throw an exception in X++, you typically do it by something like this:
throw error(«It’s broken!»);
It’s a functional equivalent of adding a message to infolog and throwing Exception::Error.
infolog.add(Exception::Error, «It’s broken»);throw Exception::Error; // number 3
As you see, an exception in X++ is (or used to be, as I’ll discuss later) just a number, because enums are backed by integer values. It doesn’t come with any extra information, not even the message. Adding messages to infolog is a separate process; you can add error messages to infolog without throwing any exceptions and throwing exceptions without adding anything to infolog.
When you want to catch an exception, you’ll use a catch clause with the right value of Exception enum (usually Exception::Error) or you’ll handle all exceptions (by a catch clause without any type).
The following statement will work for all errors, but you don’t get any information about what error it was:
catch (Exception::Error) { … }
Just knowing that there is an error doesn’t allow you to react differently to different errors, therefore you’ll rarely see code in AX trying to recover from a particular error.
CLR exceptions (in general)
It’s very different in Common Language Runtime (CLR). Exceptions there are objects (instances of classes extending System.Exception) and they contain a lot of information that can help you to identify what happened. For example, you can look at the type of exception (e.g. ArgumentNullException or FileNotFoundException) and react accordingly. The error message is a part of the object too, not just lying somewhere in infolog. The exception also comes with the stack trace, so you can easily see where it was thrown from.
Because the classes form a hierarchy, you can also handle exceptions in a hierarchic way. This is what you can write in C#:
catch (FileNotFoundException ex) { … }catch (Exception ex) { … }
The system will try catch clauses one by one, going from the top down, and will use the first compatible clause it finds. If a FileNotFoundException is thrown, the first block will be used, but all other exceptions will go to the latter one, because they’re not FileNotFoundException but they’re all compatible with System.Exception, which its their common base class.
Exception::CLRError
Sometimes you have to handle CLR exception in X++, because you can use .NET libraries from X++ and such libraries can throw their usual (CLR) exceptions. The traditional approach is catching Exception::CLRError, getting the exception object from CLRInterop::getLastException() and extracting information from it. It’s further complicated by the fact that the actual error is usually wrapped in TargetInvocationException.
This is how you can handle FileNotFoundException in X++:
try{ System.IO.File::ReadAllText(‘c:\nothing.here’);}catch (Exception::CLRError){ System.Exception wrapperEx = CLRInterop::getLastException() as System.Reflection.TargetInvocationException; if (wrapperEx) { if (wrapperEx.InnerException is System.IO.FileNotFoundException) { warning(wrapperEx.InnerException.Message); } }}
It works, but it’s cumbersome and hard to understand.
Catch with object reference
Fortunately AX 7 offers a new, much better option. Let me start by refactoring the previous example:
System.IO.FileNotFoundException fileNotFoundEx; try{ System.IO.File::ReadAllText(‘c:\nothing.here’);}catch (fileNotFoundEx){ warning(fileNotFoundEx.Message);}
This is obviously much shorter and easier to follow. In catch, I don’t have to use only values of the Exception enum anymore, I can also use exception objects. The system checks the type, selects the right catch clause and passes the exception object there. It’s almost the same as in C#, except of the fact that you can’t define the exception type directly in the catch condition; you have to declare a variable in an outer scope.
As in C#, you may have several catch clauses for different types of exceptions and benefit from the hierarchical nature of exception types. For example, the following snippet handles two types of exceptions in a special way and all remaining exceptions go to the last catch clause.
System.Exception generalEx;System.FieldAccessException accessEx;System.IO.FileNotFoundException fileNotFoundEx; try{ …}catch (accessEx){ warning(«Field access»);}catch (fileNotFoundEx){ warning(«File not found»);}catch (generalEx){ warning(ex.Message);}
ErrorException
All right, so X++ errors can be caught with catch (Exception::Error) and CLR exceptions by their particular type, but isn’t all code now executed by CLR? Does it mean that X++ exceptions use a different mechanism than CLR exceptions, or they’re just normal CLR exceptions under the hood?
You can find the answer in debugger, which reveals that “native” AX exceptions are indeed implemented as CLR exceptions:
The object (as you can see in debugger) has all usual properties, such as Message, Source, StackTrace and so on. Note that you can see the same behavior in AX 2012 if you debug CIL generated from X++.
Now what if you want to access the properties when handling an exception, e.g. to include the stack trace in a log? If you catch Exception::Error, you won’t get any details, but what if you try to catch ErrorException in the same way as any other CLR exception? If you think it should be possible, you’re right – the following piece of code successfully catches an X++ exception and shows how you can access its properties.
Microsoft.Dynamics.Ax.Xpp.ErrorException xppEx; try{ throw error(«It’s broken!»);}catch (xppEx){ this.log(xppEx.StackTrace);}
The problem that all X++ errors have the same exception type (ErrorException) is still there, therefore handling different errors in different ways is still hard, but you can now easily find which message belongs to the exception (without digging into infolog), where it was thrown from and so on.
By the way, I also wondered what would happen if I tried to catch both Exception::Error and ErrorException, because they’re internally the same thing.
Microsoft.Dynamics.Ax.Xpp.ErrorException xppEx; try { throw Exception::Error;} catch (Exception::Error){ info(«Exception::Error»);}catch (xppEx){ info(«ErrorException»);}
Such code compiles without any problem and the resulting CIL will actually contains two catch clauses for the same exception type (ErrorException). It means that the top one always wins, regardless whether it’s catch (Exception::Error) or catch (xppEx). Using both for the same try statement makes little sense, but at least we now know that nothing catastrophic happens if somebody does it by mistake.
Conclusion
The native implementation of exceptions in X++ is very limited in comparison to CLR exceptions. The inability to distinguish between different kinds of errors makes meaningful recovery from errors virtually impossible and the lack of associated information (such as the message) makes even mere logging quite cumbersome. This doesn’t apply to CLR exceptions and their handling in X++ is even easier than before, thanks to the new ability of catch clauses. The fact that we can use this approach for X++ exception as well means that we can easily work around one of the limitations and access properties such as message and stack trace even for them.
It seems that we’re just a small step from being able to work with exceptions in X++ in the same way as in .NET. If we could throw exception objects (e.g. throw new MissingRecordException()), we would be able catch this particular type of exception and implement more robust logic for recovery from errors.
It would also help if X++ was extended to support declaration of the exception type directly in catch (e.g. catch (MissingRecordException ex)).
Источник: http://dev.goshoom.net/en/2017/06/ca…tions-in-ax-7/
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору.
Exception handling in Axapta is a bit weird. Unlike C# or Java, exceptions aren’t full classes. In Axapta exceptions are defined in the enum Exception and that’s it. You can’t create your own exceptions and you can’t add data to the exception. If you want the error message you have to get it from the infolog. It’s limited but it usually doesn’t get in the way. Unless you add transactions to the mix.
The catch (no pun intended) is that the throw statement in Axapta also does an implicit ttsAbort if you’re in a transaction. And that’s where the confusion starts and computers get yelled at.
Suppose you’re doing some updates to several tables and need to stop processing when you detect an error. The simplified code would be something like this:
static void TryCatchOutsideTTS(Args _args) { ; try { ttsBegin; // ... throw error('Catch me if you can'); // ... ttsCommit; } catch (Exception::Error) { info('Gotcha'); } info('EOF'); }
When you run this everything works as you’d expect:
So far so good. Now what about catching exceptions inside a transaction? A possible scenario is a loop to update records, logging errors and continuing with the next record when an error is encountered. The code boils down to:
static void TryCatchInTTS(Args _args) { ; ttsBegin; //while select forUpdate ... try { // ... throw error('Catch me if you can'); // ... } catch (Exception::Error) { info('Gotcha'); } ttsCommit; info('EOF'); }
And this is what you get:
That’s strange. The catch block was not executed at all. Even worse, the part after the try/catch is ignored as well and the method ends immediately.
If you’re using a try/catch construct, you probably need to clean up whatever you’re doing if things go wrong. This shows there is no guarantee the catch block will be executed.
What if we add another try/catch?
static void DoubleTryCatch(Args _args) { ; try { ttsBegin; //while select forUpdate ... try { // ... throw error('Catch me if you can'); // ... } catch (Exception::Error) { info('Gotcha'); } ttsCommit; info('What about me?'); } catch (Exception::Error) { info('None shall pass'); } info('EOF'); }
Which yields:
This behaviour surprised me at first but then I realized it’s the same as throwing a new exception inside a catch block. The ttsAbort makes it impossible to execute the rest of the transaction safely, even if part of it is outside the try/catch block. So the only option is to fall back to a higher catch block.
Usually this doesn’t matter. In some cases this can get in the way. Like when you’re using resources (open files, connections, …) you really should release when you’re done. Using resources in a transaction isn’t best practice but you could end up in that situation without realizing it. Whenever you reuse code, be it a standard API or a third party module, it could do things you’re not fully aware of.
There’s no simple solution to the problem. Just be careful and test thoroughly to make sure situations like this can’t bring down a production environment.
Hi all,
Exception handling can be quite confusing in Dynamics AX, so I wanted to share some quick facts about try/catch and transactions.
The general rule is that exceptions are caught in the outer most catch, where the ttslevel is 0. This means that if you put a transaction around a try/catch, your exceptions will not be caught.
The following two jobs demonstrate that:
Transaction inside try/catch:
try
{
ttsBegin;
throw error(«an error»);
ttsCommit;
}
catch
{
info(«error caught»);
}
Output:
Error an error
Info error caught
Try/catch inside transaction:
ttsBegin;
try
{
throw error(«an error»);
}
catch
{
info(«error caught»);
}
ttsCommit;
Output:
Error an error
As you can see, the error is not caught when the transaction is around the try catch.
However, there are two exceptions to this rule.
The following code demonstrates that UpdateConflict and DupplicateKeyException can be caught inside a transaction.
Set set = new Set(Types::Enum); // a set containing all possible exceptions
SetEnumerator se; // enumerator used to loop though the set with exception
Exception exception; // used to cast the value from the set
boolean caughtInside;
;
// add all exception to a set
set.add(Exception::Break);
set.add(Exception::CLRError);
set.add(Exception::CodeAccessSecurity);
set.add(Exception::DDEerror);
set.add(Exception::Deadlock);
set.add(Exception::DuplicateKeyException);
set.add(Exception::DuplicateKeyExceptionNotRecovered);
set.add(Exception::Error);
set.add(Exception::Info);
set.add(Exception::Internal);
set.add(Exception::Numeric);
set.add(Exception::PassClrObjectAcrossTiers);
set.add(Exception::Sequence);
set.add(Exception::Timeout);
set.add(Exception::UpdateConflict);
set.add(Exception::UpdateConflictNotRecovered);
set.add(Exception::Warning);
// create enumerator
se = set.getEnumerator();
// loop all exceptions
while(se.moveNext())
{
// set flag false
caughtInside = false;
// begin outer try catch
try
{
ttsBegin;
// begin inner try catch
try
{
// cast exception
exception = se.current();
// trhow exception
throw exception;
}
catch
{
// set flag to indicate the exception was caught inside the transaction
caughtInside = true;
warning(strFmt(«%1 can be caught inside transaction», exception));
// throw exception again to catch it outside of transaction
throw exception;
}
ttsCommit;
}
catch
{
// for once, it’s ok to catch everyting
if(caughtInside)
{
warning(strFmt(«%1 can alse be caught outside of the transaction», exception));
}
else
{
info(strFmt(«%1 can only be caught outside of the transaction», exception));
}
}
}
Output:
Info Info can only be caught outside of the transaction
Info Warning can only be caught outside of the transaction
Info Deadlock can only be caught outside of the transaction
Info Error can only be caught outside of the transaction
Info Internal can only be caught outside of the transaction
Info Break can only be caught outside of the transaction
Info DDEerror can only be caught outside of the transaction
Info Sequence can only be caught outside of the transaction
Info Numeric can only be caught outside of the transaction
Info CLRError can only be caught outside of the transaction
Info CodeAccessSecurity can only be caught outside of the transaction
Warning UpdateConflict can be caught inside transaction
Warning UpdateConflict can alse be caught outside of the transaction
Info UpdateConflictNotRecovered can only be caught outside of the transaction
Warning DuplicateKeyException can be caught inside transaction
Warning DuplicateKeyException can alse be caught outside of the transaction
Info DuplicateKeyExceptionNotRecovered can only be caught outside of the transaction
Info Timeout can only be caught outside of the transaction
Info PassClrObjectAcrossTiers can only be caught outside of the transaction
It is also noteworthy that, when you enter a catch block, there has been a implicit ttsabort, so the transaction has been rolled back. However this is not true for UpdateConflict and DuplicateKeyException when they were caught inside a transaction.
Exception handling catch standard infolog / error and write to a log table
Here we will see, how we can capture the standard errors / info and write them to any table.
let’s create a class which extends runbase and add the all the required methods like Main, Run, etc.
in Run method, we use the Try and catch section, there we will call the method which will return a string as the info / error list.
public class MKSupplierInvoices extends RunBaseBatch
{
public static void main(Args _args)
{
MKSupplierInvoices supplierInvoice = MKSupplierInvoices::construct();
supplierInvoice.caption();
supplierInvoice.initQuery();
if (supplierInvoice.prompt())
{
supplierInvoice.run();
}
}
public void run()
{
VendTrans vendTrans;
try
{
//Logic
}
catch (Exception::Error)
{
ttsBegin;
errorInvoiceTable.selectForUpdate(true);
errorInvoiceTable.PostingStatus = PostingStatus::Error;
errorInvoiceTable.ErrorText = this.captureInfoMessage();
errorInvoiceTable.update();
ttsCommit;
}
catch (Exception::Deadlock)
{
retry;
}
catch (Exception::UpdateConflict)
{
if (appl.ttsLevel() != 0)
{
throw Exception::UpdateConflict;
}
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::UpdateConflictNotRecovered;
}
retry;
}
catch
{
info(infolog.text());
}
}
public str captureInfoMessage()
{
SysInfologEnumerator sysInfologEnumerator;
SysInfologMessageStruct infoMessageStruct;
ErrorMsg logMessage;
str logString;
str ret;
int i;
#Define.NewLine(‘n’)
sysInfologEnumerator = SysInfologEnumerator::newData(infolog.infologData());
while (sysInfologEnumerator.moveNext())
{
i = 1;
if (logMessage)
{
logMessage += #Newline;
}
infoMessageStruct = SysInfologMessageStruct::construct(sysInfologEnumerator.currentMessage());
while (i <= infoMessageStruct.prefixDepth())
{
logString = logString + infoMessageStruct.preFixTextElement(i) + ‘. ‘;
i++;
}
logString = logString + infoMessageStruct.message();
logMessage = logMessage + infoMessageStruct.message();
}
ret = logMessage;
return ret;
}
}