Update Conflict & Deadlock Handling in Dynamics 365 Finance & Operations
Question 11 — Optimistic concurrency, RecVersion, forUpdate, and the retry pattern.
Interview Question
RecVersion and forUpdate, and how you write a proper retry pattern.
Model Answer (Short)
D365 F&O uses Optimistic Concurrency Control (OCC) by default. Instead of locking a record when
it is read, it tracks a version number called RecVersion. When you later
update() or delete(), the kernel compares the RecVersion held in your buffer
with the one currently in the database. If they differ — meaning another session changed the row in between — it
throws an UpdateConflict exception. A deadlock occurs when two sessions each hold a
lock the other needs. Both situations are recoverable: you wrap the transaction in try/catch and use
the retry statement (with a retry-count guard) to re-run the logic on fresh data.
Detailed Explanation
Optimistic Concurrency Control (OCC)
- The default concurrency model — no update lock is taken when the record is read.
- Each record carries a
RecVersionvalue that changes on every update. - At
update()/delete()time, the buffer'sRecVersionis compared with the database's. - If they match → the update succeeds and
RecVersionis set to a new value. - If they differ → an UpdateConflict exception is thrown.
forUpdate and pessimistic locking
Adding forUpdate to a select marks the record for a coming update. Under OCC this
does not place a database update lock (UPDLock) — the conflict is still detected via
RecVersion at write time. To force a genuine pessimistic lock you would use
pessimisticLock() or the optimisticLock hint appropriately.
Deadlock
A deadlock happens when session A holds a lock that session B wants, while B holds a lock A wants — neither can
proceed. SQL Server picks a victim and D365 F&O surfaces it as an Exception::Deadlock, which
you handle by simply issuing retry.
Do not use ttsAbort
In D365 F&O you generally do not call ttsAbort: when an exception is caught
outside the transaction, the framework automatically rolls back the whole transaction. Explicit
ttsAbort is considered an anti-pattern.
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your objects.
- The
#OCCRetryCountmacro available (declare a retry-count macro if using a custom limit). - A transaction scope (
ttsbegin/ttscommit) around the update/delete.
Code Example — Full retry pattern
Handling both deadlock and update conflict with a bounded retry:
#OCCRetryCount
try
{
ttsbegin;
// ... update or delete process ...
ttscommit;
}
catch (Exception::Deadlock)
{
// Simply retry on a deadlock
retry;
}
catch (Exception::UpdateConflict)
{
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
// Give up after too many attempts
throw Exception::UpdateConflictNotRecovered;
}
else
{
retry;
}
}
else
{
throw Exception::UpdateConflict;
}
}
Exclusive control with forUpdate
protected void execute()
{
SalesLine salesLine;
LineAmount lineAmount;
ttsbegin;
while select forUpdate salesLine
{
lineAmount = this.calcLineAmount(salesLine);
salesLine.LineAmount = lineAmount;
salesLine.update(); // RecVersion checked here
}
ttscommit;
}
Set-based update to reduce conflicts
protected void execute()
{
SalesLine salesLine, salesLineForUpdate;
ttsbegin;
while select salesLine
{
LineAmount lineAmount = this.calcLineAmount(salesLine);
update_recordset salesLineForUpdate
setting LineAmount = lineAmount
where salesLineForUpdate.RecId == salesLine.RecId;
}
ttscommit;
}
Update Conflict vs. Deadlock
| Aspect | Update Conflict | Deadlock |
|---|---|---|
| Cause | Another session changed the row (RecVersion mismatch) | Two sessions each waiting on the other's lock |
| Detected by | OCC RecVersion comparison at write |
SQL Server lock manager |
| Exception | Exception::UpdateConflict |
Exception::Deadlock |
| Recovery | retry with count guard |
retry |
| Prevention | Shorter transactions, re-read before update | Consistent lock order, proper indexes |
Points the interviewer wants to hear
- D365 F&O uses Optimistic Concurrency Control by default.
RecVersionmismatch at write time triggers an UpdateConflict.- Under OCC,
forUpdatealone does not take a DB update lock. - Handle deadlock/conflict with
try/catch+retry, guarded by a retry count. - Avoid
ttsAbort— rollback is automatic on a caught exception.
Likely Follow-up Questions
- What is the difference between optimistic and pessimistic locking in D365 F&O?
- Why must you guard
retrywith a retry-count check? - Why is
ttsAbortdiscouraged in D365 F&O? - How can proper indexing reduce deadlocks in bulk updates?
Key Takeaway
D365 F&O relies on optimistic concurrency and RecVersion to detect update
conflicts, and on SQL Server to detect deadlocks. Both are recoverable with a disciplined
try/catch + retry pattern (with a retry-count guard) — and you should never resort to
ttsAbort.