bcquality/microsoft/knowledge/performance/avoid-calcfields-in-loops.md
Jesper Schulz-Wedde dc14e7bb2a Update 6 knowledge articles to align with revised instructions
- performance/use-setloadfields-for-partial-records: Clarify that
  filter-only fields (SetRange/SetFilter) do not need to be listed in
  SetLoadFields — the DB resolves them via the index without hydrating
  the value into AL memory.

- performance/avoid-calcfields-in-loops: Add explicit exception for
  OnAfterGetRecord and OnValidate triggers, which are platform-managed
  and not developer-authored loops.

- performance/split-read-only-and-write-paths-to-avoid-locktable: Add
  ReadIsolation as the primary recommendation for read-only paths;
  LockTable reserved for confirmed write paths only.

- performance/prefer-direct-record-over-recordref: Scope the finding to
  hot unbounded loops (10k+ rows) over ledger-entry-scale tables;
  RecordRef in bounded/admin/setup contexts is not a concern.

- upgrade/enum-changes-must-be-additive-at-the-end: Replace direct
  ObsoleteState = Removed guidance with the two-stage workflow (Pending
  first, Removed later); reference use-obsolete-pending-before-removed.

- upgrade/use-datatransfer-for-large-dataset-initialization: Add the
  >300,000 records threshold as the concrete trigger for requiring
  DataTransfer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 10:36:08 +02:00

29 lines
1.6 KiB
Markdown

---
bc-version: [all]
domain: performance
keywords: [calcfields, flowfield, loop, n-plus-one]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Do not call CalcFields inside loops
## Description
CalcFields evaluates one or more FlowFields for the current record by issuing a separate SQL aggregation. Called inside a loop over a record set, it becomes an N+1 problem: one aggregate per row. For any non-trivial set on a ledger-entry-backed FlowField this is orders of magnitude slower than the equivalent batched query.
## Best Practice
Move CalcFields out of the iteration. If the total is what you need, use CalcSums on the filtered parent set. If row-by-row FlowField values are needed, reshape the computation so the aggregate runs once — for example by joining against a temporary table populated in a single batched query.
**Acceptable exceptions:** CalcFields inside an `OnAfterGetRecord` page trigger is the standard pattern for displaying computed FlowField values — the platform calls this trigger once per row and it is not a developer-authored loop. Similarly, CalcFields inside an `OnValidate` field trigger fires at most once per user action and is acceptable. The concern is only developer-written `FindSet … repeat … until Next() = 0` loops.
See sample: `avoid-calcfields-in-loops.good.al`.
## Anti Pattern
Calling CalcFields inside `repeat ... until Next() = 0` on a hot parent record is the textbook N+1 pattern. Even a modest parent set size (hundreds of rows) turns into thousands of round-trips.
See sample: `avoid-calcfields-in-loops.bad.al`.