Transactions & Transaction Scope in Dynamics 365 Finance & Operations
Question 47 — ttsbegin/ttscommit, rollback behaviour, and when to break the transaction scope.
Interview Question
ttsbegin / ttscommit model, the default rollback
behaviour, why TTS blocks must be balanced, and when you would deliberately break the
transaction scope (e.g. header-level commits).
Model Answer (Short)
A transaction groups database changes so they either all succeed or all fail together. You open it
with ttsbegin and confirm it with ttscommit. The rule is
that DB data is basically written in one transaction, and if an error occurs the whole thing rolls
back — automatically, without calling ttsAbort. TTS blocks must be
clear and well-balanced (every ttsbegin paired with a ttscommit), and
multiple/nested TTS blocks should be avoided where possible. Sometimes, however, a requirement
needs to commit at header-unit level and continue even if some lines fail — in that case you must
consult the designer to agree the correct transaction scope rather than wrapping everything in a
single all-or-nothing transaction.
Transaction Rules
Core rules
- DB data is basically written in one transaction; on error the whole rollback is performed.
- Some functions need to commit to header units and continue even if there are errors — this requires consulting the designer on the transaction scope.
- Keep
ttsbegin/ttscommitclear and well-balanced. - Avoid multiple/nested TTS blocks wherever applicable.
- Do not call
ttsAbort— rollback is automatic when an exception is caught.
Exclusive control & getting update records
- When updating/deleting output data, a lock must be obtained for the record to be updated.
- Under optimistic locking,
forUpdatedoes not take a DB update lock (UPDLock). - At
update()/delete(), the buffer's RecVersion is compared with the DB's; a mismatch raises an exception. - The update/delete succeeds only if RecVersion matches, then RecVersion is set to a new value.
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your objects.
- A clear design decision on transaction scope (single vs. header-level commit).
- The
#OCCRetryCountmacro if you combine transactions with retry logic.
Code Example — Single all-or-nothing transaction
protected void execute()
{
SalesLine salesLine;
LineAmount lineAmount;
ttsbegin; // one transaction wraps all updates
while select forUpdate salesLine
{
lineAmount = this.calcLineAmount(salesLine);
salesLine.LineAmount = lineAmount;
salesLine.update(); // RecVersion checked here
}
ttscommit; // commit only if the whole loop succeeded
}
Header-level commit (break scope by design)
// Commit per header so a failure on one header doesn't roll back the others.
// NOTE: this pattern must be agreed with the designer.
AbcOrderHeader header;
AbcOrderLine line;
while select header
{
try
{
ttsbegin;
while select forUpdate line
where line.OrderId == header.OrderId
{
line.Processed = NoYes::Yes;
line.update();
}
header.Processed = NoYes::Yes;
header.update();
ttscommit; // commit this header unit
}
catch (Exception::Error)
{
// This header rolled back automatically; continue with the next
error(strFmt("Header %1 failed and was skipped.", header.OrderId));
}
}
Avoid: unbalanced / nested TTS & ttsAbort
// BAD: extra ttsbegin without a matching commit, plus ttsAbort
ttsbegin;
ttsbegin; // avoid unnecessary nesting
// ...
ttsAbort; // avoid — rollback is automatic on exception
// missing ttscommit -> unbalanced
// GOOD: one balanced block, let exceptions roll back automatically
ttsbegin;
// ...
ttscommit;
Single Transaction vs. Header-Level Commit
| Aspect | Single transaction | Header-level commit |
|---|---|---|
| Failure behaviour | Whole batch rolls back | Only the failing header rolls back |
| Data consistency | Strongest (all-or-nothing) | Per-unit consistency |
| Use when | All records must succeed together | Processing must continue past errors |
| Design approval | Default approach | Must consult the designer |
Points the interviewer wants to hear
- Data is written in one transaction; on error the whole batch rolls back.
- Keep TTS balanced and avoid multiple/nested blocks.
- Never call
ttsAbort— rollback is automatic. - For header-level commit scenarios, agree the scope with the designer.
- A lock must be obtained on records being updated; OCC checks RecVersion.
Likely Follow-up Questions
- What happens to a transaction when an exception is thrown inside it?
- Why should multiple/nested TTS blocks be avoided?
- When would you deliberately commit at header level instead of one transaction?
- Does
forUpdatetake a database lock under optimistic concurrency?
Key Takeaway
Write DB changes in a single, balanced transaction that rolls back entirely on error (no
ttsAbort). Only break into header-level commits when the requirement demands
continuing past failures — and always agree that scope with the designer.