Exception Handling in Transactions (Dynamics 365 Finance & Operations)
Question 24 — throw / try / catch / finally, automatic rollback, and why you avoid ttsAbort.
Interview Question
ttsbegin…ttscommit) in Dynamics 365 Finance &
Operations? Explain the throw / try / catch / finally flow, why you should not call
ttsAbort, and how to catch a generic exception.
Model Answer (Short)
In D365 F&O you raise errors with throw and handle them with
try / catch / finally. The key point about transactions is that when an exception is
thrown inside a ttsbegin…ttscommit block and caught outside the transaction, the
framework automatically rolls back the whole transaction — so you generally do
not call ttsAbort yourself (it's considered an anti-pattern). A catch
block cannot be placed inside the same transaction scope it protects; it must sit at a lower tts level.
To catch everything you catch System.Exception, and you handle specific recoverable
cases like Deadlock and UpdateConflict with retry.
Detailed Explanation
How it behaves in a transaction
- A thrown exception inside
ttsbegin…ttscommittriggers an automatic rollback when caught outside the transaction. - The
catchblock must be at a lower tts level than the transaction it guards — you can't catch within the same open transaction. - Recoverable exceptions like Deadlock and UpdateConflict are handled with
retry. finallyalways runs — ideal for cleanup that must happen regardless of success/failure.
Why avoid ttsAbort
- The framework already rolls back the transaction automatically on a caught exception.
- Explicit
ttsAbortis redundant and considered an anti-pattern in D365 F&O. - It can mask the real error flow and complicate nested transactions.
- Let exceptions propagate and rely on automatic rollback instead.
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your objects.
- A transaction scope (
ttsbegin/ttscommit) around the data changes. - The
#OCCRetryCountmacro if you useretrywith a bounded count.
Code Example — Catching a generic exception
System.Exception ex;
try
{
ttsbegin;
// ... data operations ...
ttscommit;
}
catch (ex)
{
// Read message/stack from the .NET exception
str message = strFmt("@AbcLabels:GenericError", ex.get_Message());
error(message);
}
Handling deadlock & update conflict with retry
#OCCRetryCount
try
{
ttsbegin;
// ... update / delete ...
ttscommit;
}
catch (Exception::Deadlock)
{
retry; // simply retry on deadlock
}
catch (Exception::UpdateConflict)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::UpdateConflictNotRecovered;
}
else
{
retry;
}
}
Using finally for guaranteed cleanup
AbcResource res = new AbcResource();
try
{
ttsbegin;
res.doWork();
ttscommit;
}
catch (Exception::Error)
{
error("Operation failed; transaction rolled back automatically.");
}
finally
{
// Always runs — release the resource no matter what
res.close();
}
Common Exception Types to Catch
| Exception | Meaning | Typical handling |
|---|---|---|
Exception::Error |
General error thrown via throw error() |
Log / show message |
Exception::Deadlock |
SQL deadlock victim | retry |
Exception::UpdateConflict |
OCC RecVersion mismatch | retry with count guard |
System.Exception |
Any .NET/CLR exception | Catch-all last resort |
Points the interviewer wants to hear
- An exception caught outside the tts scope triggers automatic rollback.
- Do not call
ttsAbort— it's redundant and an anti-pattern. - You can't
catchinside the same open transaction; catch at a lower tts level. - Handle Deadlock / UpdateConflict with
retry(bounded). finallyalways runs — use it for cleanup.
Likely Follow-up Questions
- Why is
ttsAbortdiscouraged in D365 F&O? - Why can't a
catchblock sit inside the same transaction it protects? - Which exceptions are recoverable with
retry? - When would you catch
System.Exceptionversus a specific type?
Key Takeaway
In D365 F&O, throwing an exception inside a transaction and catching it outside the tts scope
rolls everything back automatically — so skip ttsAbort. Handle recoverable
deadlocks and update conflicts with a bounded retry, and use finally
for guaranteed cleanup.