Add 15 community knowledge articles from BC Code Intel ingest (#66)
Some checks failed
Validate knowledge index / validate-index (push) Has been cancelled
Validate frontmatter and structure / validate (push) Has been cancelled

* Add 15 community knowledge articles from BC Code Intel ingest

Ingests net-new /community knowledge from BC Code Intelligence, surviving
the admission test, gray-zone salvage, and dedup against the full corpus.

Domains: ui (6), error-handling (3), performance (2), upgrade (1),
appsource (1), security (1), telemetry (1). The two BC24 No. Series
migration drafts are merged into one article.

Adds good/bad AL samples for the clean-fit articles (error-handling,
performance, security, telemetry). UI and appsource remain knowledge-only.

Validator and knowledge-index checks pass (207 articles).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Correct SetLoadFields JIT-load article to match MS docs

The draft claimed accessing an unlisted field "reloads the entire row"
per record. Microsoft's partial-records docs say otherwise: the platform
does an implicit Get that loads the missing field(s), and in a direct var
loop the first JIT updates the enumerator so later iterations do not
re-load. The genuine per-row penalty is the pass-by-value case, where the
copy's enumerator is not updated.

Rewrite the article around JIT loading and the by-value footgun, rename
the slug from ...full-reload to ...jit-load, and fix the good/bad samples
to demonstrate the by-value repetition accurately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Jeremy Vyska <jeremy@sparebrained.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeremy Vyska 2026-07-01 14:31:50 +02:00 committed by GitHub
parent 6281e7e39a
commit 4119417ce4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 613 additions and 0 deletions

View file

@ -0,0 +1,11 @@
codeunit 50130 "Purge Orders Bad Sample"
{
procedure PurgeCancelledLines(var SalesLine: Record "Sales Line")
begin
// Assumes DeleteAll fires OnDelete and cascades to reservation entries
// and item applications. It does not: parameterless DeleteAll() is
// DeleteAll(false) and skips OnDelete, so the rows vanish but their
// dependent records are orphaned.
SalesLine.DeleteAll();
end;
}

View file

@ -0,0 +1,16 @@
codeunit 50130 "Purge Orders Good Sample"
{
procedure PurgeCancelledLines(var SalesLine: Record "Sales Line")
begin
// These lines have OnDelete cleanup (reservation entries, item
// application). Pass true so DeleteAll runs OnDelete per record and the
// cleanup actually happens the row-by-row cost is accepted on purpose.
SalesLine.DeleteAll(true);
end;
procedure PurgeStagingBuffer(var TempBuffer: Record "Name/Value Buffer" temporary)
begin
// No OnDelete logic to run: the fast, set-based form is correct here.
TempBuffer.DeleteAll();
end;
}

View file

@ -0,0 +1,24 @@
---
bc-version: [all]
domain: performance
keywords: [deleteall, ondelete, run-trigger, set-based-delete, bulk-delete, triggers, validation]
technologies: [al]
countries: [w1]
application-area: [all]
---
# DeleteAll skips OnDelete unless you pass RunTrigger
> Contributions welcome — open a PR to refine or extend this article.
## Description
`Record.DeleteAll()` — equivalently `DeleteAll(false)` — translates to a single set-based SQL `DELETE` and **does not** run AL `OnDelete` triggers or field/table validations. Only database-level referential constraints still apply. To run `OnDelete` logic you must call `DeleteAll(true)`, which then deletes record-by-record and forfeits the set-based performance, making it equivalent to a `FindSet` loop calling `Delete(true)`. The common misconception, which training data reproduces, is that `DeleteAll` iterates and fires `OnDelete` per record; it does not. (Parameterless `Delete()` likewise defaults to `Delete(false)` and skips `OnDelete`.)
## Best Practice
Use `DeleteAll()` / `DeleteAll(false)` for bulk deletion only when no AL `OnDelete` cleanup is required — it is the fast, set-based form. When `OnDelete` logic must run (cascading deletes, ledger cleanup, integration events), pass `DeleteAll(true)` and accept the row-by-row cost, or refactor the cleanup to run explicitly before the bulk delete.
## Anti Pattern
Calling `DeleteAll()` and assuming dependent records, integration events, or validation side effects are handled by `OnDelete`. The deletion succeeds but the AL-side cleanup never runs, leaving orphaned data — and adding a manual `FindSet`/`Delete` loop "for safety" reintroduces the per-record cost the set-based form was chosen to avoid.

View file

@ -0,0 +1,26 @@
codeunit 50132 "LoadFields Bad Sample"
{
procedure TotalReleasedAmount(): Decimal
var
SalesHeader: Record "Sales Header";
Total: Decimal;
begin
// "Currency Code" is not listed. The helper takes SalesHeader BY VALUE,
// so the copy neither shares the load set nor updates the enumerator:
// reading the unlisted field triggers a fresh JIT load (an extra Get)
// on EVERY iteration, quietly reversing the saving.
SalesHeader.SetLoadFields("Amount Including VAT", Status);
if SalesHeader.FindSet() then
repeat
if IsLocalReleased(SalesHeader) then
Total += SalesHeader."Amount Including VAT";
until SalesHeader.Next() = 0;
exit(Total);
end;
local procedure IsLocalReleased(SalesHeader: Record "Sales Header"): Boolean
begin
exit((SalesHeader.Status = SalesHeader.Status::Released) and
(SalesHeader."Currency Code" = ''));
end;
}

View file

@ -0,0 +1,24 @@
codeunit 50132 "LoadFields Good Sample"
{
procedure TotalReleasedAmount(): Decimal
var
SalesHeader: Record "Sales Header";
Total: Decimal;
begin
// Every field read anywhere downstream is listed including the one
// the by-var helper reads so no JIT load is ever triggered.
SalesHeader.SetLoadFields("Amount Including VAT", Status, "Currency Code");
if SalesHeader.FindSet() then
repeat
if IsLocalReleased(SalesHeader) then
Total += SalesHeader."Amount Including VAT";
until SalesHeader.Next() = 0;
exit(Total);
end;
local procedure IsLocalReleased(var SalesHeader: Record "Sales Header"): Boolean
begin
exit((SalesHeader.Status = SalesHeader.Status::Released) and
(SalesHeader."Currency Code" = ''));
end;
}

View file

@ -0,0 +1,24 @@
---
bc-version: [all]
domain: performance
keywords: [setloadfields, partial-records, just-in-time-load, jit-load, round-trip, pass-by-value, enumerator]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Reading an unlisted field after SetLoadFields triggers a JIT load
> Contributions welcome — open a PR to refine or extend this article.
## Description
`SetLoadFields` loads only the named fields, but the trap is what happens when code later reads a field that was *not* listed: the platform silently issues a **just-in-time (JIT) load** — an implicit `Get` that fetches the missing field(s) in a second database round-trip. A single JIT load can erase the saving; the real danger is a JIT that repeats per record. The optimization is only a win if the listed set covers every field touched anywhere downstream, not just in the immediate code block.
## Best Practice
Before adding `SetLoadFields`, audit the *whole* access lifecycle of the record variable — every field read in the loop body, in called procedures, in `OnValidate`/`OnAfterGetRecord`, and in anything that receives the record — and list all of them via `SetLoadFields`/`AddLoadFields`. Be especially careful when passing a partial record **by value**: the copy does not share the load set and its enumerator is not updated, so a helper that reads an unlisted field re-triggers the JIT on *every* iteration. Pass by `var` where you can (a JIT then updates the enumerator, so later iterations don't re-load), or call `AddLoadFields` before passing by value. If you cannot enumerate the fields confidently, prefer not to call `SetLoadFields` at all. See the existing guidance on when partial records pay off (`use-setloadfields-for-partial-records`).
## Anti Pattern
Adding `SetLoadFields(Field1, Field2)` at the top of a loop, then reading `Field3` deeper in the body or inside a by-value helper. The code compiles and returns correct data, but pays a hidden JIT round-trip — and in the by-value case it repeats once per row, quietly reversing the gain. JIT loads also introduce `Inconsistent read` / record-modified race errors that a full non-partial load avoids. Reviewer signal: a `SetLoadFields` list that omits a field later read through that record variable, especially a record passed by value to a procedure that reads a field the caller never listed.