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>
This commit is contained in:
Jeremy Vyska 2026-07-01 11:02:48 +02:00
parent 6281e7e39a
commit f5156c61de
29 changed files with 601 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,20 @@
codeunit 50132 "LoadFields Bad Sample"
{
procedure TotalReleasedAmount(): Decimal
var
SalesHeader: Record "Sales Header";
Total: Decimal;
begin
// "Currency Code" is not in the list. Reading it each iteration forces
// a second database round-trip that reloads the WHOLE row N full
// reloads, slower than never calling SetLoadFields at all.
SalesHeader.SetLoadFields("Amount Including VAT", Status);
if SalesHeader.FindSet() then
repeat
if (SalesHeader.Status = SalesHeader.Status::Released) and
(SalesHeader."Currency Code" = '') then
Total += SalesHeader."Amount Including VAT";
until SalesHeader.Next() = 0;
exit(Total);
end;
}

View file

@ -0,0 +1,18 @@
codeunit 50132 "LoadFields Good Sample"
{
procedure TotalReleasedAmount(): Decimal
var
SalesHeader: Record "Sales Header";
Total: Decimal;
begin
// Every field read in the loop is listed, so each row stays a cheap
// partial load with no hidden second round-trip.
SalesHeader.SetLoadFields("Amount Including VAT", Status);
if SalesHeader.FindSet() then
repeat
if SalesHeader.Status = SalesHeader.Status::Released then
Total += SalesHeader."Amount Including VAT";
until SalesHeader.Next() = 0;
exit(Total);
end;
}

View file

@ -0,0 +1,24 @@
---
bc-version: [all]
domain: performance
keywords: [setloadfields, partial-records, just-in-time-load, field-reload, round-trip, lazy-load]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Touching an unlisted field after SetLoadFields forces a full-row reload
> 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 second database round-trip and reloads the **entire row** for that record — per record. In a loop, a single overlooked field turns one cheap partial read into N full-row reloads, which is slower than never calling `SetLoadFields` at all. 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 by reference — and list all of them. If you cannot enumerate them confidently (for example the record is passed to code you do not control), prefer not to call `SetLoadFields` rather than risk the reload penalty. 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 in a helper. The code compiles and returns correct data, but each iteration pays a hidden full-row reload — the change reads as an optimization while regressing performance. Reviewer signal: a `SetLoadFields` list that does not include every field subsequently referenced through that record variable.