Skip to content

Transaction and error handling in BC AL - new knowledge articles#11

Open
Drakonian wants to merge 1 commit intomicrosoft:mainfrom
Drakonian:TransactionAndErrorHandling
Open

Transaction and error handling in BC AL - new knowledge articles#11
Drakonian wants to merge 1 commit intomicrosoft:mainfrom
Drakonian:TransactionAndErrorHandling

Conversation

@Drakonian
Copy link
Copy Markdown

@Drakonian Drakonian commented Apr 23, 2026

Transaction and error handling in BC AL - new knowledge articles

In my experience, many Business Central developers, including experienced ones, struggle with AL's transaction and error-handling model. It differs enough from SQL transaction patterns and from general try/catch semantics in other languages that reasoning about it without BC-specific guidance leads to real bugs: Commits inside loops, database writes wrapped in [TryFunction] expecting rollback, event publishers unprotected from subscriber Commits, tests that die at the first Commit without any business-logic verdict.

I tried to consolidate what I know about this area into a small, coherent set of articles so that a review agent citing any one of them naturally surfaces the rules that go with it.

What I added

Performance:

  • understand-implicit-transaction-boundary.md - the foundational article: AL manages write transactions automatically; Commit() splits an execution, it does not start one.
  • codeunit-run-as-atomic-sub-operation.md (+ good/bad samples) - if Codeunit.Run(X) then is the AL-idiomatic rollback boundary; a direct contrast with SQL BEGIN TRAN habits.
  • codeunit-run-requires-prior-commit-inside-transaction.md (+ good/bad samples) - the BC rule that Codeunit.Run cannot nest inside an open write transaction, and the loop trap that forces readers into per-row Commits.
  • use-tryfunction-for-error-catching-not-rollback.md (+ good/bad samples) - the biggest single misconception: [TryFunction] catches errors but does not roll back database writes. Also covers the return-capture rule and the shared GetLastErrorText buffer.

Security:

  • commitbehavior-attribute-scopes-explicit-commits.md (+ good/bad samples) - the [CommitBehavior(::Ignore)] pattern for protecting an atomic publisher from third-party subscriber Commits.

Testing (new domain /microsoft/knowledge/testing/):

  • transactionmodel-attribute-governs-test-transactions.md (+ good/bad samples) - matching [TransactionModel] to the commit behavior of the code under test; the first article in a new domain for test-infrastructure concerns.

What I changed

  • avoid-commit-inside-loops.md - enriched with BC context (the runtime's implicit end-of-execution commit, the Codeunit.Run-based checkpoint alternative) and now ships a good.al sample demonstrating the chunked-checkpoint pattern. Both the good and bad samples were rewritten on a neutral operation (Customer.Name normalization), replacing the earlier unrealistic direct Sales Header status assignment.

How the articles reference each other

Seven interlinked articles that a review agent can walk:

  • avoid-commit-inside-loops.mdunderstand-implicit-transaction-boundary.md, codeunit-run-as-atomic-sub-operation.md
  • understand-implicit-transaction-boundary.mdavoid-commit-inside-loops.md, codeunit-run-as-atomic-sub-operation.md
  • codeunit-run-as-atomic-sub-operation.mdcodeunit-run-requires-prior-commit-inside-transaction.md, use-tryfunction-for-error-catching-not-rollback.md
  • codeunit-run-requires-prior-commit-inside-transaction.mdcodeunit-run-as-atomic-sub-operation.md, avoid-commit-inside-loops.md, use-tryfunction-for-error-catching-not-rollback.md
  • commitbehavior-attribute-scopes-explicit-commits.mdcodeunit-run-requires-prior-commit-inside-transaction.md, use-tryfunction-for-error-catching-not-rollback.md
  • use-tryfunction-for-error-catching-not-rollback.mdcodeunit-run-as-atomic-sub-operation.md
  • transactionmodel-attribute-governs-test-transactions.md - standalone; the test-infrastructure concern doesn't intersect with the production-runtime articles above.

Each article stays narrow enough to be cited alone. A finding about [TryFunction] wrapping writes cites just the TryFunction article; a finding that touches atomic sub-operations and the prior-commit rule cites both and lets the reader walk the graph for the rest.

Scope and what I left out

I kept scope to the core primitives - Commit, Codeunit.Run, [TryFunction], [CommitBehavior], [TransactionModel] - and the specific interactions between them that developers demonstrably get wrong. A few things I considered but left out:

  • Cursor stability across Commit. The community folklore is real but poorly documented at Microsoft. Without an authoritative source or an empirical test, including it would fail BCQuality's own admission test.
  • Database.IsInWriteTransaction(). Documented but too thin to carry its own article. I added a short note in the prior-commit article's Anti Pattern section, framing it as a diagnostic tool and a code smell when used for production branching.
  • On-premises DisableWriteInsideTryFunctions. Noted inline in the TryFunction article; not expanded, since the behavior is invisible in SaaS.

Samples use ClearLastError() immediately before any GetLastErrorText() read, matching the Microsoft documentation idiom. Codeunit IDs are in the 50128–50156 range; none collide with existing samples.

P.S. I use MS Learn documentation to validate every article, so this is why I point it to microsoft folder, let me know if I need to change it to community

@Drakonian
Copy link
Copy Markdown
Author

@JesperSchulz
I'm not sure if you're already accepting PRs, but this might be helpful 🐱

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant