[Master]-Opening price list from Customer Card is extremely slow on large datasets (~220s) due to AddAllSourceType call#9571
Conversation
…lter BuildSourceFilters in PriceListManagement.Codeunit.al now iterates the PriceSourceList exactly once. The OnBuildSourceFiltersOnBeforeFindLines event fires once per source (matches original firing semantics). When only one source has matches, its full filter view (including subscriber-added filters) is captured with GetView(false) and restored with SetView, avoiding the O(N) FindSet+Mark scan over 'All Customers' price lines that made opening the Sales Prices page take ~220s on large datasets. When multiple sources match, we fall back to marking. Adds T220 and T221 tests. Fixes bug 641061.
| until not PriceSourceList.Next(PriceSource); | ||
| ClearSourceFilters(PriceListLine); | ||
|
|
||
| if NonEmptyCount = 1 then |
There was a problem hiding this comment.
In the rewritten BuildSourceFilters (PriceListManagement.Codeunit.al, local procedure at line 751), when every price source in PriceSourceList is empty (NonEmptyCount stays 0), MarkingIsUsed is left false and the trailing 'else ClearSourceFilters(PriceListLine)' branch (line 792-795) removes the Source Type/Parent Source No./Source No.
filters entirely. Because the caller (SetPriceListLineFilters, line 642-643) only calls PriceListLine.MarkedOnly(true) when MarkingIsUsed is true, no marking restriction is applied either. The net effect is that PriceListLine ends up filtered only by Status/Price Type/Amount Type, i.e. it returns every price line in the system for that status/type/amount combination instead of an empty result -- a real regression versus the pre-change behavior, where ClearSourceFilters was always paired with an unconditional MarkedOnly(true), so zero marked lines correctly produced zero results. The immediately adjacent, structurally identical BuildAssetFilters/CheckIfPriceListLineMarkingIsNeeded pair in the same codeunit (line 711-712) explicitly guards this exact case with 'if RecordSetsCounter = 0 then exit(true)' to force MarkedOnly(true) when no asset matched; BuildSourceFilters' new NonEmptyCount logic omits the equivalent guard for the 0-match case. Neither new test (T220, T221) exercises a customer/context where every price source is empty, so this path is untested. Recommend forcing MarkingIsUsed := true (or otherwise ensuring the result set is empty) when NonEmptyCount = 0, mirroring CheckIfPriceListLineMarkingIsNeeded. This would otherwise be rated major/blocker (functional correctness: unrelated customers' or items' price lines become visible), but is capped at minor here per agent-finding severity rules since no BCQuality knowledge article covers this exact case.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // "Customer Disc. Group" is added to the price source list by the Customer Card, but that source type only | ||
| // supports Discount amount type and is therefore filtered out by the Sales Prices action. | ||
| LibrarySales.CreateCustomer(Customer); | ||
| Customer."Customer Price Group" := CustomerPriceGroup.Code; |
There was a problem hiding this comment.
In T221_SalesPriceLinesFromCustomerCardMultipleSourceTypesSharingEmptyParent, the fixture assigns Customer."Customer Price Group" and Customer."Customer Disc.
Group" directly and calls Modify(), bypassing the TableRelation and any OnValidate logic on those foreign-key fields. Use Customer.Validate on each field before Modify so the fixture exercises the same setup rules as production code.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
Customer.Validate("Customer Price Group", CustomerPriceGroup.Code);
Customer.Validate("Customer Disc. Group", CustomerDiscountGroup.Code);
Customer.Modify(true);Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Copilot PR ReviewIteration 2 · Outcome: completed
Knowledge source: https://github.com/microsoft/BCQuality@186d8a131465475c79244d994acb872cd5c0d4bf Orchestrator pre-filter (2 file(s) excluded)
Findings produced by the AL review agent v1.7.3. Reply 👎 on any inline comment to flag false positives. |
qasimikram
left a comment
There was a problem hiding this comment.
S1 (blocking): Please preserve an empty result when none of the price sources match. In BuildSourceFilters, NonEmptyCount = 0 currently leaves MarkingIsUsed false and then calls ClearSourceFilters. The caller consequently skips MarkedOnly(true), leaving only the status, price type, and amount type filters and potentially showing unrelated price lines. Please handle the zero-match case explicitly and add a regression test where every source is empty.
Question on performance coverage: The new fast path avoids marking only when exactly one source has matching rows. When both All Customers and a customer-specific or group source contain rows, NonEmptyCount reaches 2 and the implementation returns to marking every matching row, including a potentially very large All Customers set. Could you confirm one of the following?
- The production reproduction has only one non-empty source, with before/after timing showing that this change meets the <5-second target.
- The multi-source scenario has also been tested at scale and remains acceptably fast when both
All Customersand customer/group rows exist.
When none of the price sources have matching lines, BuildSourceFilters now sets MarkingIsUsed := true (with no marks) so the caller applies MarkedOnly(true), returning an empty set. Previously the caller skipped MarkedOnly, leaving only the Status / Price Type / Amount Type filters and potentially showing unrelated price lines. Adds regression test T222_SalesPriceLinesFromCustomerCardNoMatchingSourceLinesShowsEmpty covering the case where unrelated sales price lines exist but none match the current customer's sources.
qasimikram
left a comment
There was a problem hiding this comment.
The zero-match regression is addressed: NonEmptyCount = 0 now forces MarkedOnly(true), and T222 covers the case where unrelated price lines exist but none match the current customer's sources.
One performance-coverage question remains. The fast path avoids marking only when exactly one source has matching rows. If both All Customers and a customer-specific or group source contain rows, NonEmptyCount reaches 2 and the implementation still marks every matching row, including a potentially large All Customers set. Could you confirm one of the following?
- The production reproduction has only one non-empty source, with before/after timing showing that this change meets the <5-second target.
- The multi-source scenario has also been tested at scale and remains acceptably fast when both
All Customersand customer/group rows exist.
Non-blocking test suggestion: use Validate for the customer group fields and Modify(true) in T221/T222 so the fixtures exercise the same validation path as production setup.
| MarkingIsUsed := true; | ||
| CurrentMatchView := PriceListLine.GetView(false); | ||
| PriceListLine.SetView(FirstMatchView); | ||
| MarkPriceListLines(PriceListLine); |
There was a problem hiding this comment.
Could you confirm the performance coverage for this fallback path? When both All Customers and a customer-specific or group source contain rows, this branch still marks every matching row, including a potentially large All Customers set.
Please confirm either that the production reproduction has only one non-empty source with before/after timing meeting the <5-second target, or that this multi-source path was tested at scale and remains acceptably fast.
qasimikram
left a comment
There was a problem hiding this comment.
The blocking zero-match regression is fixed and covered by T222. I found no remaining correctness issue in the updated diff. Approving with the open performance-coverage thread left for confirmation before merge.
AB#641061