Fixes technical inaccuracies and behavioral issues raised during review of the community performance knowledge articles. avoid-currpage-update-in-onaftergetrecord.md - Removed OnAfterGetCurrRecord from the Best Practice section. That trigger is itself implicitly re-entered on every refresh, so placing CurrPage.Update(false) inside it can re-trigger the very loop the rule warns about. Only OnAction remains as the recommended location. batch-number-series-instead-of-getnextno-per-row.md - Scoped the lock/contention claim to gapless (Normal) series only. Added explicit statement that Allow Gaps series use NumberSequence sequences and do not hold the series-line lock, so the anti-pattern detection signal now excludes Allow Gaps series. countapprox-for-progress-not-count.bad.al / .good.al - Changed FindSet(true) to FindSet() in both samples. The loop is read-only; UpdLock is not needed and was misleading. countapprox-for-progress-not-count.md - Qualified the Count() cost claim: it is expensive only when no SIFT key covers all filtered fields (forcing a SELECT COUNT(*)); a filtered count with matching SIFT coverage is cheap. Added a parenthetical noting that SIFT coverage cannot be assumed for arbitrary filters. dataaccessintent-readonly-on-analytical-objects.md - Changed bc-version from [all] to ["16.."]. DataAccessIntent was introduced at runtime 5.0 / BC 16 and has no effect in earlier versions. - Added precision to the supported objects: pages must be PageType=API with Editable=false; for queries, replica routing only applies when the query is exposed via OData/API, not for AL-to-AL calls. httpclient-inside-write-transaction-holds-locks.good.al - Replaced the Commit()-before-HttpClient pattern with a two-codeunit task-deferral pattern. The write completes inside the caller's transaction (locks released naturally when it ends); a TaskScheduler task runs the HTTP call in a separate session where no write- transaction lock is held. httpclient-inside-write-transaction-holds-locks.md - Changed Best Practice to recommend TaskScheduler/job queue deferral as the primary remedy. - Added an explicit warning against Commit() as a generic remedy: it irrevocably commits all prior writes in the current transaction, so a subsequent failure cannot roll them back. Commit() is appropriate only at top-level entry points where partial persistence is intentional. oncompanyopen-subscribers-must-not-do-io.good.al - Added a ClientType guard so the subscriber exits immediately in background task sessions (OnAfterLogin fires there too, which would create an unbounded task chain without the guard). - Added a TaskScheduler.TaskExists idempotency check to avoid queuing duplicate tasks on repeated logins. - Fixed the error-fallback codeunit in CreateTask from a self-reference to 0 (no error codeunit). - Added a 60-second delay (CurrentDateTime() + 60000) so the task does not compete with the login session itself. prefer-related-table-over-extension-on-hot-ledgers.md - Changed bc-version from [all] to ["23.."]. The companion-table join optimisation (single join per base table, automatic exclusion on List/ OData pages with partial records) was introduced in v23. - Scoped the "join is always paid" claim: since v23 the join is excluded on List/ListPart/OData pages when no extension field is loaded under partial-record semantics, but it is still paid on every posting path and any AL code that accesses an extension field. skip-setloadfields-on-write-and-transferfields.bad.al / .good.al / .md - Changed the example scenario from Modify(false) (which is actually valid with a partial record) to TransferFields+Insert into a temporary record, which is a documented full-load operation. - Removed Modify from the list of operations that force a full load. - Added an explicit note in the .md that Modify itself is not in the full-load list; SetLoadFields is safe to use before Modify(false). use-dedicated-lookup-pages-not-full-lists.bad.al - Added a separate CardPart page definition (50101) to replace the self-referencing FactBox part that referenced the same list page it was embedded in. A part cannot refer to its own container page. validate-on-partial-record-forces-jit.good.al - Restored SetLoadFields to the good sample so it exercises a partial record and the contrast with .bad.al is field selection, not the absence of the feature. The good sample loads both Name and Search Name (the field Name.OnValidate writes), while the bad sample loads only Name, causing a JIT reload of Search Name on every Validate call.
1.8 KiB
| bc-version | domain | keywords | technologies | countries | application-area | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
performance |
|
|
|
|
Batch number-series calls instead of GetNextNo per insert
Contributions welcome — open a PR to refine or extend this article.
Description
Codeunit "No. Series".GetNextNo on a gapless (Normal) series updates and locks the number-series line on every call. A tight Insert loop that asks for a number per row serializes every concurrent writer on that series — the classic SaaS posting bottleneck. Training data still copies the per-row C/AL NoSeriesManagement shape. Series configured with Allow Gaps instead obtain numbers through NumberSequence and do not hold the series-line lock between calls, so they are not affected by this pattern. Codeunit "No. Series - Batch" issues gapless numbers in memory and writes the series line once via SaveState.
Best Practice
Inside a multi-row insert, call "No. Series - Batch".GetNextNo per row and SaveState once after the loop when the series must remain gapless. Use NumberSequence.Next when holes are allowed. Do not replace a single OnInsert GetNextNo for one master record; that path is not the hotspot.
See sample: batch-number-series-instead-of-getnextno-per-row.good.al.
Anti Pattern
NoSeries.GetNextNo(...) inside repeat ... Insert ... until Next() = 0 where the series is gapless (Allow Gaps = false). Each iteration takes the series-line lock. The signal is "No. Series" (not "No. Series - Batch") in a loop that inserts more than one row; do not flag the same pattern when the series has Allow Gaps enabled, as the NumberSequence path already avoids the lock.
See sample: batch-number-series-instead-of-getnextno-per-row.bad.al.