If you've been developing extensions in Business Central AL for any length of time, you've almost certainly written code in the OnAfterGetRecord trigger on a list page. It feels natural — the record is loaded, so that's where you put your logic, right?
Wrong — and this single misunderstanding is behind some of the worst performance issues we see on real customer environments. Slow list scrolling, sluggish page loads, excessive SQL round trips — a surprising number of them trace directly back to this mistake.
In this post, we're going to break down the exact difference between OnAfterGetRecord and OnAfterGetCurrRecord, show you when to use each, and walk through real AL code examples that demonstrate the right approach.
Understanding the Triggers
OnAfterGetRecord
The OnAfterGetRecord trigger fires for every single record that the platform renders into the list view. This means if a user scrolls through 200 rows, this trigger fires 200 times. If they sort the list, it fires again. If they resize the browser window and the grid re-renders additional rows, it fires again.
The intended purpose of this trigger is lightweight, row-level operations — specifically things like computing a StyleExpr variable that controls how a row is visually formatted (bold, unfavorable, strong, etc.). That's it. That's the sweet spot.
OnAfterGetCurrRecord
The OnAfterGetCurrRecord trigger fires only when the user changes their selection — i.e., moves focus from one row to another. It fires once per selection change, and it fires on the currently highlighted record.
This makes it the safe, correct home for any logic that involves looking up related records, computing page-level variables, refreshing FactBoxes, or setting action visibility based on the current record's state.
Side-by-Side Comparison
|
Aspect |
OnAfterGetRecord |
OnAfterGetCurrRecord |
|
Fires on |
Every rendered row |
Selected row only |
|
Frequency |
N times per scroll |
Once per row focus |
|
Use for |
StyleExpr, row-level format |
FactBoxes, actions, vars |
|
Avoid |
GET, FIND, CALCFIELDS |
Nothing — safe here |
|
Performance risk |
High — SQL per row |
Low — single call |
The Problem in Practice
Here is the pattern we see far too often in the wild. A developer needs to show a count of related Sales Lines next to each Sales Header in a list. They write this:
|
❌ Wrong approach This code fires a database GET for every row rendered — causing N+1 SQL queries. |
// ❌ BAD: OnAfterGetRecord with expensive database call trigger OnAfterGetRecord() begin // This fires for EVERY row the list renders into view SalesLine.Reset(); SalesLine.SetRange("Document Type", Rec."Document Type"); SalesLine.SetRange("Document No.", Rec."No."); LineCount := SalesLine.Count(); end;
On a list with 50 visible rows, this fires 50 separate SETRANGE + COUNT queries. On a slow VPN or a cloud tenant under load, this is immediately visible to the end user as a laggy, janky scroll experience.
The Correct Approach
Move expensive logic to OnAfterGetCurrRecord
If the logic only needs to run for the selected row — for example, to update a FactBox or populate a page variable shown in a summary tile — move it entirely to OnAfterGetCurrRecord:
|
✅ Correct approach OnAfterGetCurrRecord fires once per user selection — not once per rendered row. |
// ✅ GOOD: OnAfterGetCurrRecord for selection-dependent logic
trigger OnAfterGetCurrRecord()
begin
// Fires only when the user clicks a different row
SalesLine.Reset();
SalesLine.SetRange("Document Type", Rec."Document Type");
SalesLine.SetRange("Document No.", Rec."No.");
LineCount := SalesLine.Count();
// Update FactBox or page-level variable safely here
CurrPage.SalesLineFactbox.Page.SetRecord(Rec);
end;
Keep OnAfterGetRecord minimal
If you genuinely need per-row logic — most commonly for row styling — keep OnAfterGetRecord fast and free of any database calls:
// ✅ GOOD: OnAfterGetRecord for lightweight StyleExpr only
var
StyleExpr: Text;
trigger OnAfterGetRecord()
begin
// Only compute visual style — no DB calls
case Rec.Status of
Rec.Status::Blocked:
StyleExpr := 'Unfavorable';
Rec.Status::Released:
StyleExpr := 'Favorable';
else
StyleExpr := '';
end;
end;
The StyleExpr field is then referenced in the field's Style property on the page:
field("No."; Rec."No.")
{
ApplicationArea = All;
Style = StyleExpr; // References the computed variable
StyleExpr = StyleExpr;
}
|
Found this useful? If this tip helped you catch a performance issue in your BC extension, share it with your team. Subscribe to the blog for more AL development tips covering performance, architecture, AppSource readiness, and real-world patterns. |



