bcquality/microsoft/knowledge/performance/al-methods-limited-during-write-transactions.md
Michael Dieringer 56ce52a9c7
AL methods limited during write transactions (RunModal, Codeunit.Run) (#161)
* Add runmodal-is-not-allowed-inside-write-transactions.md; drop "modal page" from the prompts article

A modal page does not behave like Confirm/StrMenu inside a write
transaction: the platform refuses Page.RunModal (and Report/XmlPort
.RunModal with a request page, and Codeunit.Run with its return value
used) with a runtime error instead of holding the lock. The new article
documents that guard - verified against Microsoft Learn (Codeunit.Run
transaction semantics), microsoft/AL#5452, Microsoft's own Base
Application (Commit(); Page.RunModal pattern), and a live reproduction
on Business Central 26 quoted verbatim. avoid-user-prompts-inside-
transactions.md keeps its scope to the prompts the platform does allow;
"modal page" is removed from its list because that case is refused, not
stalled.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Do not list Message among the lock-holding prompts

Message runs asynchronously - it is queued and shown when the calling
method ends or another method requests input - so it never pauses the
transaction and holds no lock. Only Confirm and StrMenu wait for the
user.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* State the four write-transaction conditions as one explicit line per method

Mirrors the structure of the platform's own error message so the
Report.RunModal and XmlPort.RunModal request-page exceptions are
visible at a glance instead of buried in prose.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Title the article after the platform error it explains

"AL methods limited during write transactions: commit before RunModal
and Codeunit.Run" - so a developer or agent searching for the runtime
error text lands on the one article that covers all four restricted
methods. Slug and sample stems renamed to match; keywords gain the
error's own phrase and the legacy Form.RunModal name it still uses.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Trim keywords to 10 per validator guidance

* Wire the new article into al-performance-review.md

Adds Page.RunModal / Report.RunModal / XmlPort.RunModal / UseRequestPage
to the extracted-token list and one deterministic worklist cue with
exclusions, so the article is selected from the RunModal call itself
rather than only via a co-located Commit/Modify token. Codeunit.Run in
that position is routed to its existing owner article.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Correct XmlPort.RunModal to Xmlport.Run; fix READ-convention sample links

XmlPort has no RunModal method (static or instance) - Microsoft Learn
confirms only Xmlport.Run(Integer [, Boolean RequestWindow] [, Boolean]
[, var Record]). Replaces the invented API with the real one throughout
the article, worklist cue, and token list, and explains the platform
error message's own "XmlPort.RunModal" wording as the same kind of
legacy phrasing already noted for Form.RunModal/RequestForm.

Also corrects UseRequestPage(false): that's a Report instance method
only, not applicable to XMLport, which uses the UseRequestPage = false
object property or Run's RequestWindow argument instead.

Fixes the two sample references to use the READ-convention markdown
link form (Test-KnowledgeIndex.ps1's Knowledge-Retrieval.ps1 check was
failing on plain backtick text).

Rebased onto upstream/main to resolve conflicts with #148's job-queue
additions to al-performance-review.md - both sets of worklist tokens
and cues are retained.

* Add Report.Run alongside Report.RunModal to the write-transaction guard routing

al-performance-review.md's tokens/cues only recognized Report.RunModal,
so a failing Modify(); Report.Run(..., true, ...) path was never
worklisted even though Report.Run shares the exact same RequestWindow-
blocking-dialog mechanism as Report.RunModal (they differ only in
whether the report instance is cleared afterward). Added Report.Run to
the token list and the targeted cue, with the same request-page-
suppressed exclusion, and extended the knowledge article's Description
bullet to name both methods explicitly instead of only RunModal.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-09-29 17:13:35 +02:00

7.8 KiB

bc-version domain keywords technologies countries application-area
all
performance
limited-during-write-transactions
write-transaction
runmodal
report-run
page-runmodal
report-runmodal
xmlport-run
codeunit-run
commit
requestpage
al
w1
all

AL methods limited during write transactions: commit before RunModal and Codeunit.Run

Description

Once AL code has written to the database in the current transaction — an Insert, Modify, or Delete with no Commit since — the platform restricts four methods until that transaction is committed. The exact conditions, as enforced:

  • Page.RunModal — not allowed in a write transaction, under any circumstances.
  • Report.RunModal and Report.Run — both allowed only if the request page is suppressed: Report.RunModal(ReportId, false) / Report.Run(ReportId, false) (the second argument is RequestWindow in both), or UseRequestPage(false) on a report instance. With a request page shown, either fails identically — Run and RunModal differ only in whether the report instance is cleared afterward, not in this guard.
  • Xmlport.Run — same rule when it would show a request page: allowed only if the request page is suppressed, Xmlport.Run(XmlPortId, false) (the second argument is RequestWindow) or the UseRequestPage = false; object property. With a request page shown it fails. There is no XmlPort.RunModal method — see the note on the platform's error text below.
  • Codeunit.Run — allowed only if its Boolean return value is not used. OK := Codeunit.Run() and if Codeunit.Run() then fail, because that form commits — see codeunit-run-requires-prior-commit-inside-transaction.md.

This is a runtime error, not a compiler diagnostic: the code builds, and the first execution that reaches the call with an open write transaction dies. The guard keys on transaction state alone, not on any relation between what was written and what is opened: an Item.Insert() followed by Page.RunModal(Page::"Customer Card") — an unrelated table — fails on the RunModal line, and because the error stops the transaction, the insert rolls back with it.

The platform's message (Business Central 26, reproduced 2026-09-07) reads: "The following AL methods are limited during write transactions because one or more tables will be locked: Form.RunModal, Codeunit.Run, Report.RunModal, XmlPort.RunModal. Form.RunModal is not allowed in write transactions. Codeunit.Run is allowed in write transactions only if the return value is not used. For example, 'OK := Codeunit.Run()' is not allowed. Report.RunModal is allowed in write transactions only if 'RequestForm = false'. For example, 'Report.RunModal(...,false)' is allowed. XmlPort.RunModal is allowed in write transactions only if 'RequestForm = false'. For example, 'XmlPort.RunModal(...,false)' is allowed. Use the commit method to save the changes before this call, or structure the code differently." The message still uses the legacy names Form.RunModal and RequestForm even though the AL method is Page.RunModal and the report parameter is RequestWindow; older versions said "C/AL functions" instead of "AL methods" (microsoft/AL#5452, 2019). The message also names XmlPort.RunModal, which is legacy phrasing too — there is no such AL method; the actual API the guard applies to is Xmlport.Run, and the message's RequestForm = false condition corresponds to Xmlport.Run's RequestWindow argument or the UseRequestPage property. The behavior is unchanged across versions.

The reason is the same one behind avoid-user-prompts-inside-transactions.md: a modal object waits for the user, and the platform will not let a write transaction — and every lock it holds — sit open for as long as that takes. The difference is enforcement. Confirm and StrMenu are allowed inside a write transaction and silently hold the locks; RunModal is refused. Both point at the same design fix.

Best Practice

Sequence the work so the modal interaction happens before the write phase: run the lookup or dialog page first, then perform the writes the user's choice requires, and let the transaction end. When a modal object genuinely must follow a write, Commit() first — but only when the state written so far is complete and safe to persist on its own, because that Commit is a real transaction boundary, not a formality. Microsoft's own Base Application follows exactly this pattern where the preceding state is final (ActivityLog.Table.al commits the log entry before Page.RunModal(Page::"Activity Log", Rec); DocumentSendingProfile.Table.al commits before Page.RunModal(Page::"Select Sending Options", …)). For a report, suppressing the request page — Report.UseRequestPage(false) on a report instance, or false as the RequestWindow argument to Run/RunModal — is a legitimate way to run it inside a write transaction when no user input is needed. For an XMLport, the equivalent is false as the RequestWindow argument to Xmlport.Run, or the UseRequestPage = false; object property; XMLports have no instance UseRequestPage method. Database.IsInWriteTransaction() (runtime 11.0+) lets library code that cannot control its caller detect the state, with the same caveat as the Codeunit.Run article: branching production flow on it usually signals unclear transaction ownership.

See sample: al-methods-limited-during-write-transactions.good.al.

Anti Pattern

Writing to the database and then calling Page.RunModal (or a report/XMLport with its request page) in the same trigger — the first production run hits the runtime error. The reflexive fixes are worse than the error: dropping in Commit() to silence it persists a half-finished state that can no longer roll back with the rest of the operation, and swapping the page for a Confirm or StrMenu to "avoid the error" trades a loud failure for the silent lock-holding that avoid-user-prompts-inside-transactions.md warns about.

See sample: al-methods-limited-during-write-transactions.bad.al.

Source