TWItys Blog

All Around Microsoft Dynamics Business Central

Business Central Confirm Can Keep Your Transaction Open

Confirm looks harmless.

It is just a yes/no dialog, right?

But in Business Central, there is a nasty habit that shows up again and again in custom code: developers do some work, touch one or more records, and then call Confirm() before the code finishes.

That is a bad pattern because the user is now part of your transaction.

And while the user is thinking, clicking, getting coffee, or being interrupted by a Teams call, the transaction can stay open.

Why this is a problem

In AL, write transactions are started automatically when needed and are normally committed when the code finishes. If you display a Confirm dialog after you have already modified data or taken locks, you are effectively pausing that unit of work in the middle.

From a technical point of view, the code is waiting for user input, but the transaction has not been completed yet.

That means other sessions may have to wait for locks to be released.

The result is usually not dramatic in development. In production, though, it often turns into exactly the kind of issue that is hard to reproduce and annoying to diagnose:

  • Users complain that "Business Central is slow"
  • Another session suddenly waits on locked records
  • Posting or batch jobs appear blocked for no obvious reason
  • Problems disappear as soon as the first user clicks Yes or No

That is why Confirm inside a write transaction is such a bad habit. It hides a concurrency problem behind a perfectly innocent UI dialog.

The typical anti-pattern

Here is the kind of code that should make you nervous:

local procedure UpdateCustomerCreditLimit(CustomerNo: Code[20]; NewLimit: Decimal)
var
    Customer: Record Customer;
begin
    Customer.Get(CustomerNo);
    Customer.LockTable();
    Customer."Credit Limit (LCY)" := NewLimit;
    Customer.Modify();

    if not Confirm('Do you want to continue with the related updates?') then
        exit;

    DoMoreWork(Customer);
end;

The problem is not the question itself.

The problem is when the question is asked.

At that point, the code has already changed data. If the user waits 30 seconds before answering, that transaction may stay open for 30 seconds as well. If the user goes for lunch, you now have a much bigger problem.

"But it works"

Yes, it works.

That is exactly why this pattern survives.

It works in happy-path tests, and it works when there is only one developer in a sandbox. The damage shows up later, under concurrency, when several users or background processes hit the same data.

This is one of those patterns that is functionally correct and operationally dangerous.

The safer pattern: ask first, write later

If you need user confirmation, get it before you start changing data.

local procedure UpdateCustomerCreditLimit(CustomerNo: Code[20]; NewLimit: Decimal)
var
    Customer: Record Customer;
begin
    if not Confirm('Do you want to update the credit limit and continue with related updates?') then
        exit;

    Customer.Get(CustomerNo);
    Customer.LockTable();
    Customer."Credit Limit (LCY)" := NewLimit;
    Customer.Modify();

    DoMoreWork(Customer);
end;

This small reorder changes a lot:

  • The user interaction happens before the transaction becomes interesting
  • Locks are held for a shorter time
  • Other sessions are less likely to be blocked
  • The code behaves better under real-world usage

If the question is purely about whether the operation should start, ask it at the beginning.

What about Commit() before Confirm?

Some developers solve this by inserting a Commit() before the dialog:

Customer.Modify();
Commit();

if not Confirm('Continue?') then
    exit;

Yes, this can release locks before the user interaction.

But it also splits your work into multiple transactions, and that is a business decision, not just a technical cleanup.

Once you commit, the first part is permanent. If the user answers No, or if the next step fails, you cannot pretend the whole operation was still one atomic unit.

So Commit() is not the universal fix. Sometimes it is the right tool, but only when you consciously accept the new transaction boundary.

A useful rule of thumb

When you see Confirm, ask yourself one question:

"Has this code already modified data or taken locks before the user is asked?"

If the answer is yes, stop and rethink the flow.

That simple review habit catches a surprising number of hidden blocking issues.

Where this usually shows up

I most often see this problem in:

  • Posting customizations
  • Setup wizards and assisted actions
  • Batch processing with optional follow-up steps
  • Refactoring of old C/AL patterns that were copied into AL without revisiting the transaction flow

In all of these cases, the dialog often feels user-friendly, but it can quietly turn into a lock-duration problem.

My recommendation

Confirm is not bad.

Using Confirm inside an open write transaction is bad.

So my default recommendation is:

  1. Ask for confirmation before writing anything
  2. Keep transactions short
  3. Treat Commit() as an explicit transaction design choice, not a quick escape hatch
  4. Review old code for dialogs that appear after Modify(), Insert(), Delete(), or explicit locking

That one habit can remove a surprising amount of avoidable blocking from a Business Central solution.

Final thought

Whenever a user dialog appears in the middle of data-changing code, remember what is really happening:

your database work may now be waiting for a human.

And humans are much slower than SQL Server.