✏️ Explanatory Question

How do transactions work in Dynamics 365 Finance & Operations? Explain the 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).

👁 3 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

D365 F&O • X++ INTERVIEW

Transactions & Transaction Scope in Dynamics 365 Finance & Operations

Question 47 — ttsbegin/ttscommit, rollback behaviour, and when to break the transaction scope.

Interview Question

How do transactions work in Dynamics 365 Finance & Operations? Explain the 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/ttscommit clear and well-balanced.
  • Avoid multiple/nested TTS blocks wherever applicable.
  • Do not call ttsAbort — rollback is automatic when an exception is caught.
GOLDEN RULE
All-or-nothing  •  Balanced TTS  •  Header-level commit? Ask the designer

Exclusive control & getting update records

  • When updating/deleting output data, a lock must be obtained for the record to be updated.
  • Under optimistic locking, forUpdate does 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 #OCCRetryCount macro 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 forUpdate take 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.