Seed performance knowledge corpus (22 articles + AL samples)

Converts an existing performance-review prompt into 22 atomic
knowledge articles under microsoft/knowledge/performance/, each
paired with AL samples under samples/performance/<slug>/ demonstrating
the anti-pattern and/or the best practice. The full set seeds the
corpus the microsoft/skills/al-performance-review leaf skill matches
against and validates the READ knowledge-file format end-to-end.

Every article conforms to the READ contract: six required frontmatter
fields, Description always present, no fenced code blocks, sample
code referenced by repo-relative path. Each article is marked with a
blockquote 'Seed article' note so domain stewards can extend or
restructure them freely.

Articles (ordered by concern area):

Database query efficiency
- use-findset-with-next (AA0181)
- avoid-findfirst-with-next (AA0233)
- only-fetch-records-you-use (AA0175)
- use-findset-readonly-by-default
- use-setloadfields-for-partial-records
- use-addloadfields-in-report-layouts
- use-calcsums-to-aggregate-filtered-sets (file: use-calcsums-for-flowfield-totals.md)
- avoid-calcfields-in-loops
- add-sift-keys-for-flowfields (AA0232)
- use-isempty-for-existence-checks

Filter and key optimization
- filter-before-find
- set-current-key-to-match-filters

Temporary tables and transactions
- use-temporary-tables-for-intermediate-data
- keep-transaction-scope-short
- avoid-user-interaction-in-transactions
- avoid-commit-inside-loops

Record operations
- prefer-get-for-primary-key-lookups
- use-insert-false-when-skipping-triggers
- prefer-direct-record-over-recordref

Strings, codeunits, events
- use-strsubstno-for-message-formatting
- use-single-instance-codeunits-for-caching
- keep-event-subscribers-lightweight

samples/README.md documents the sample-folder convention and makes
clear the samples are demonstration-only, not derived from BC base
application source, with unique object IDs in the 50100-50199 range.

Rubber-duck pass caught: a misleading good.al in avoid-calcfields-in-loops
(fixed by switching to a hoistable CalcFields scenario), an invalid
event subscriber signature in keep-event-subscribers-lightweight
(fixed by adding var xRec), normative guidance leaked into the
Description of use-findset-readonly-by-default (moved to Anti Pattern),
a missing sample pair for keep-transaction-scope-short (added), and
muddy FlowField/CalcSums framing (retitled and clarified).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-04-17 13:23:34 +02:00
parent 5aaa58e8ee
commit 32c40bbf1d
58 changed files with 1110 additions and 0 deletions

View file

@ -0,0 +1,10 @@
tableextension 50118 "Perf Sample SIFTKey" extends "Cust. Ledger Entry"
{
keys
{
key(PerfSampleOpenByCustomer; "Customer No.", Open, "Posting Date")
{
SumIndexFields = "Remaining Amt. (LCY)";
}
}
}

View file

@ -0,0 +1,18 @@
codeunit 50117 "Perf Sample CalcFieldsInLoop Bad"
{
procedure ProcessLargeLines(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line")
begin
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindSet() then
repeat
SalesHeader.CalcFields(Amount);
if SalesHeader.Amount > 1000 then
ProcessLine(SalesLine);
until SalesLine.Next() = 0;
end;
local procedure ProcessLine(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,18 @@
codeunit 50116 "Perf Sample CalcFieldsInLoop Good"
{
procedure ProcessLargeLines(var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line")
begin
SalesHeader.CalcFields(Amount);
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindSet() then
repeat
if SalesHeader.Amount > 1000 then
ProcessLine(SalesLine);
until SalesLine.Next() = 0;
end;
local procedure ProcessLine(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,15 @@
codeunit 50129 "Perf Sample CommitInLoop Bad"
{
procedure ReleaseAllOrders()
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange(Status, SalesHeader.Status::Open);
if SalesHeader.FindSet() then
repeat
SalesHeader.Status := SalesHeader.Status::Released;
SalesHeader.Modify();
Commit();
until SalesHeader.Next() = 0;
end;
}

View file

@ -0,0 +1,14 @@
codeunit 50105 "Perf Sample AvoidFindFirstNext Bad"
{
procedure EmitAllItems(var Item: Record Item)
begin
if Item.FindFirst() then
repeat
EmitItem(Item);
until Item.Next() = 0;
end;
local procedure EmitItem(var Item: Record Item)
begin
end;
}

View file

@ -0,0 +1,11 @@
codeunit 50127 "Perf Sample UserInTxn Bad"
{
procedure ArchiveSalesHeader(var SalesHeader: Record "Sales Header")
begin
SalesHeader.Status := SalesHeader.Status::Released;
SalesHeader.Modify();
if not Confirm('Archive document %1?', false, SalesHeader."No.") then
exit;
SalesHeader.Delete(true);
end;
}

View file

@ -0,0 +1,14 @@
codeunit 50126 "Perf Sample UserInTxn Good"
{
procedure ArchiveSalesHeader(var SalesHeader: Record "Sales Header")
begin
if not Confirm('Archive document %1?', false, SalesHeader."No.") then
exit;
DoArchive(SalesHeader);
end;
local procedure DoArchive(var SalesHeader: Record "Sales Header")
begin
// only Insert/Modify/Delete calls happen here; no prompts
end;
}

View file

@ -0,0 +1,16 @@
codeunit 50101 "Perf Sample FilterBeforeFind Bad"
{
procedure ProcessUsCustomers(var Customer: Record Customer)
begin
if Customer.FindSet() then
repeat
if Customer."Country/Region Code" = 'US' then
ProcessCustomer(Customer);
until Customer.Next() = 0;
end;
local procedure ProcessCustomer(var Customer: Record Customer)
begin
// per-customer work
end;
}

View file

@ -0,0 +1,16 @@
codeunit 50100 "Perf Sample FilterBeforeFind Good"
{
procedure ProcessUsCustomers(var Customer: Record Customer)
begin
Customer.SetRange("Country/Region Code", 'US');
if Customer.FindSet() then
repeat
ProcessCustomer(Customer);
until Customer.Next() = 0;
end;
local procedure ProcessCustomer(var Customer: Record Customer)
begin
// per-customer work
end;
}

View file

@ -0,0 +1,12 @@
codeunit 50140 "Perf Sample Subscriber Bad"
{
[EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnAfterValidateEvent', 'No.', false, false)]
local procedure HeavyWorkOnSalesLineNo(var Rec: Record "Sales Line"; var xRec: Record "Sales Line")
var
HttpClient: HttpClient;
HttpResponse: HttpResponseMessage;
begin
// synchronous external call on a hot event
HttpClient.Get('https://example.com/validate?no=' + Rec."No.", HttpResponse);
end;
}

View file

@ -0,0 +1,18 @@
codeunit 50128 "Perf Sample TxnScope Bad"
{
procedure ImportCustomers(var Source: List of [Text])
var
Customer: Record Customer;
HttpClient: HttpClient;
HttpResponse: HttpResponseMessage;
Row: Text;
begin
foreach Row in Source do begin
// external call inside the write transaction
HttpClient.Get('https://example.com/validate?row=' + Row, HttpResponse);
Customer.Init();
// ... populate from Row ...
Customer.Insert(true);
end;
end;
}

View file

@ -0,0 +1,22 @@
codeunit 50123 "Perf Sample TxnScope Good"
{
procedure ImportCustomers(var Source: List of [Text])
var
Prepared: Record Customer temporary;
Customer: Record Customer;
begin
// read, validate, and shape outside the transaction
PrepareRows(Source, Prepared);
// transaction starts here: only Insert/Modify calls
if Prepared.FindSet() then
repeat
Customer := Prepared;
Customer.Insert(true);
until Prepared.Next() = 0;
end;
local procedure PrepareRows(var Source: List of [Text]; var Prepared: Record Customer temporary)
begin
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50107 "Perf Sample OnlyFetchUsed Bad"
{
procedure CustomerHasEntries(CustomerNo: Code[20]): Boolean
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
exit(CustLedgerEntry.FindSet());
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50106 "Perf Sample OnlyFetchUsed Good"
{
procedure CustomerHasEntries(CustomerNo: Code[20]): Boolean
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
exit(not CustLedgerEntry.IsEmpty());
end;
}

View file

@ -0,0 +1,20 @@
codeunit 50135 "Perf Sample RecordRef Bad"
{
procedure BlockCustomer(CustomerNo: Code[20])
var
RecRef: RecordRef;
PkRef: KeyRef;
NoRef: FieldRef;
BlockedRef: FieldRef;
begin
RecRef.Open(Database::Customer);
PkRef := RecRef.KeyIndex(1);
NoRef := PkRef.FieldIndex(1);
NoRef.SetRange(CustomerNo);
if not RecRef.FindFirst() then
exit;
BlockedRef := RecRef.Field(54);
BlockedRef.Value(2);
RecRef.Modify(true);
end;
}

View file

@ -0,0 +1,12 @@
codeunit 50134 "Perf Sample RecordRef Good"
{
procedure BlockCustomer(CustomerNo: Code[20])
var
Customer: Record Customer;
begin
if not Customer.Get(CustomerNo) then
exit;
Customer.Blocked := Customer.Blocked::All;
Customer.Modify(true);
end;
}

View file

@ -0,0 +1,11 @@
codeunit 50131 "Perf Sample GetVsFind Bad"
{
procedure CustomerName(CustomerNo: Code[20]): Text[100]
var
Customer: Record Customer;
begin
Customer.SetRange("No.", CustomerNo);
if Customer.FindFirst() then
exit(Customer.Name);
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50130 "Perf Sample GetVsFind Good"
{
procedure CustomerName(CustomerNo: Code[20]): Text[100]
var
Customer: Record Customer;
begin
if Customer.Get(CustomerNo) then
exit(Customer.Name);
end;
}

View file

@ -0,0 +1,9 @@
codeunit 50122 "Perf Sample SetCurrentKey Good"
{
procedure LinesForDocument(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]; var SalesLine: Record "Sales Line")
begin
SalesLine.SetCurrentKey("Document Type", "Document No.", "Line No.");
SalesLine.SetRange("Document Type", DocumentType);
SalesLine.SetRange("Document No.", DocumentNo);
end;
}

View file

@ -0,0 +1,17 @@
report 50112 "Perf Sample AddLoadFields Good"
{
dataset
{
dataitem(Cust; "Cust. Ledger Entry")
{
column(CustomerNo; "Customer No.") { }
column(PostingDate; "Posting Date") { }
column(Amount; Amount) { }
trigger OnPreDataItem()
begin
AddLoadFields("Customer No.", "Posting Date", Amount);
end;
}
}
}

View file

@ -0,0 +1,14 @@
codeunit 50115 "Perf Sample CalcSums Bad"
{
procedure OutstandingForCustomer(CustomerNo: Code[20]) Total: Decimal
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.SetRange(Open, true);
if CustLedgerEntry.FindSet() then
repeat
Total += CustLedgerEntry."Remaining Amt. (LCY)";
until CustLedgerEntry.Next() = 0;
end;
}

View file

@ -0,0 +1,12 @@
codeunit 50114 "Perf Sample CalcSums Good"
{
procedure OutstandingForCustomer(CustomerNo: Code[20]): Decimal
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.SetRange(Open, true);
CustLedgerEntry.CalcSums("Remaining Amt. (LCY)");
exit(CustLedgerEntry."Remaining Amt. (LCY)");
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50109 "Perf Sample FindSetReadonly Bad"
{
procedure SumInvoiceLines(var SalesLine: Record "Sales Line") Total: Decimal
begin
if SalesLine.FindSet(true) then
repeat
Total += SalesLine."Line Amount";
until SalesLine.Next() = 0;
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50108 "Perf Sample FindSetReadonly Good"
{
procedure SumInvoiceLines(var SalesLine: Record "Sales Line") Total: Decimal
begin
if SalesLine.FindSet() then
repeat
Total += SalesLine."Line Amount";
until SalesLine.Next() = 0;
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50103 "Perf Sample FindSetWithNext Bad"
{
procedure SumLineAmounts(var SalesLine: Record "Sales Line") Total: Decimal
begin
if SalesLine.FindFirst() then
repeat
Total += SalesLine."Line Amount";
until SalesLine.Next() = 0;
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50102 "Perf Sample FindSetWithNext Good"
{
procedure SumLineAmounts(var SalesLine: Record "Sales Line") Total: Decimal
begin
if SalesLine.FindSet() then
repeat
Total += SalesLine."Line Amount";
until SalesLine.Next() = 0;
end;
}

View file

@ -0,0 +1,12 @@
codeunit 50132 "Perf Sample InsertParam Good"
{
procedure BulkLoadTempItems(var TempItem: Record Item temporary; Source: List of [Code[20]])
var
ItemNo: Code[20];
begin
foreach ItemNo in Source do begin
TempItem."No." := ItemNo;
TempItem.Insert(false);
end;
end;
}

View file

@ -0,0 +1,11 @@
codeunit 50121 "Perf Sample IsEmpty Bad"
{
procedure HasOpenDocuments(CustomerNo: Code[20]): Boolean
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Sell-to Customer No.", CustomerNo);
SalesHeader.SetRange(Status, SalesHeader.Status::Open);
exit(SalesHeader.Count() > 0);
end;
}

View file

@ -0,0 +1,11 @@
codeunit 50120 "Perf Sample IsEmpty Good"
{
procedure HasOpenDocuments(CustomerNo: Code[20]): Boolean
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Sell-to Customer No.", CustomerNo);
SalesHeader.SetRange(Status, SalesHeader.Status::Open);
exit(not SalesHeader.IsEmpty());
end;
}

View file

@ -0,0 +1,14 @@
codeunit 50111 "Perf Sample SetLoadFields Bad"
{
procedure ExportItemNumbers(var Item: Record Item)
begin
if Item.FindSet() then
repeat
Export(Item."No.", Item.Description);
until Item.Next() = 0;
end;
local procedure Export(ItemNo: Code[20]; Description: Text[100])
begin
end;
}

View file

@ -0,0 +1,15 @@
codeunit 50110 "Perf Sample SetLoadFields Good"
{
procedure ExportItemNumbers(var Item: Record Item)
begin
Item.SetLoadFields("No.", Description);
if Item.FindSet() then
repeat
Export(Item."No.", Item.Description);
until Item.Next() = 0;
end;
local procedure Export(ItemNo: Code[20]; Description: Text[100])
begin
end;
}

View file

@ -0,0 +1,17 @@
codeunit 50138 "Perf Sample SingleInstance Good"
{
SingleInstance = true;
var
Cached: Record "Sales & Receivables Setup";
Loaded: Boolean;
procedure GetSetup(): Record "Sales & Receivables Setup"
begin
if not Loaded then begin
Cached.Get();
Loaded := true;
end;
exit(Cached);
end;
}

View file

@ -0,0 +1,7 @@
codeunit 50137 "Perf Sample StrSubstNo Bad"
{
procedure CustomerGreeting(var Customer: Record Customer): Text
begin
exit('Hello, ' + Customer.Name + ' (' + Customer."No." + ')');
end;
}

View file

@ -0,0 +1,9 @@
codeunit 50136 "Perf Sample StrSubstNo Good"
{
procedure CustomerGreeting(var Customer: Record Customer): Text
var
GreetingLbl: Label 'Hello, %1 (%2)';
begin
exit(StrSubstNo(GreetingLbl, Customer.Name, Customer."No."));
end;
}

View file

@ -0,0 +1,16 @@
codeunit 50124 "Perf Sample TempTable Good"
{
procedure BuildAffectedItems(var TempItem: Record Item temporary)
var
SalesLine: Record "Sales Line";
begin
TempItem.Reset();
TempItem.DeleteAll();
SalesLine.SetRange(Type, SalesLine.Type::Item);
if SalesLine.FindSet() then
repeat
TempItem."No." := SalesLine."No.";
if TempItem.Insert(false) then;
until SalesLine.Next() = 0;
end;
}