D365 F&O BP check warnings: the most common ones and how to fix them
The most common X++ best practice (BP) rules in D365 F&O: nested loops, insert in a loop, needless forUpdate, try inside tts… and how to fix them.
The short answer
"BP checks" are the best practice rules run by the X++ compiler (xppbp.exe tool). They report errors, warnings and information on code and metadata. Microsoft documents part of them in the Customization Analysis Report (CAR), with each rule's message and expected fix. The most common ones concern data access: nested loops, row-by-row inserts, select forUpdate with no update, try mishandled inside a transaction.
Good practice: fix rather than suppress the warning, and only document an exception when the fix would change the business logic.
Data access rules
- BPCheckNestedLoopInCode ("Nested data access loop found"): a while select loop inside another loop. Fix: use a join.
- BPCheckSelectwithJoin ("Nested select statement … can be joined"): nested selects with no join. Fix: join.
- BPCheckInsertMethodInLoop ("Insert method can be replaced with RecordInsertList"): insert() in a loop. Fix: set-based operations, see insert_recordset and update_recordset.
- BPCheckSelectForUpdateAbsent: select forUpdate with no update. Fix: plain select, or set OCC Enabled to Yes on the table.
- BPCheckSQLCode: direct SQL in X++. Fix: access the database in X++.
- Function called inside a select: assign the value to a local variable first, then use the variable in the select.
Transactions, forms and metadata
BPCheckNoTTSTryBlock ("Tts block with Try statement does not explicitly catch exceptions"): inside a tts block, a generic catch fails; catch the exception explicitly, for example UpdateConflict:
ttsbegin;
try
{
// ...
}
catch (Exception::UpdateConflict) // passes: explicit exception
{
// ...
}Other frequent rules:
- BPCheckSQLQueryInInit: a query with a loop in a form's init; move it.
- BPCheckinvalidExecuteQuery: an addRange after super() in executeQuery; put it before.
- BPCheckDisplayMethodCached: display method not cached; set Cache Data Method or use SysClientCacheDataMethodAttribute.
- BPCheckParametersModified: a parameter modified inside the method; modify it on the caller side.
- BPCheckAlternateKeyAbsent: table with a unique index but no alternate key; set Alternate Key to Yes.
- BPCheckMissingDeleteActions: relation with no delete action; set a value other than None.
- BPCheckEmptyLoop: empty loop; remove it.
How do you run the analysis on a model?
In a development environment, xppbp.exe generates the CAR report (Excel workbook) for a model:
xppbp.exe -metadata=<packages> -all -model=<Model> -xmlLog=C:\temp\BPCheckLogcd.xml -module=<Package> -car=c:\temp\CAReport.xlsxThe CAR is one of the requirements of solution certification for ISVs. For a customer, it is mostly a quality audit before an update or a code takeover. BP warnings also show at build time in Visual Studio.
Having these warnings fixed
Fixing a batch of BP warnings is a good ticket: well bounded and verifiable by rebuilding. Skalp AI writes the specification, generates the X++ code and unit tests, compiles with a best-practice check, then delivers a pull request to your Azure DevOps: nothing is merged without your technical architect's approval. See D365 F&O One Version updates, the bigger picture in building X++ without a consultant in 2026, and the Basic, Pro and Max plans (1 credit, ≈ €400 on Basic, €200 on Pro, consumed only when the code is generated).
See also: ISV development automation with AI and Skalp AI for D365 ISVs.
Sources
- Microsoft Learn, "Customization Analysis Report (CAR)", updated 26 Feb 2026, accessed 18 Oct 2026.
- Microsoft Learn, "Write best practice rules", updated 30 Mar 2026, accessed 18 Oct 2026.
Frequently asked questions
Should you fix every BP warning?
Ideally yes. Microsoft allows documenting an exception when the fix would change the business logic, for example for some nested loops.
What is the Customization Analysis Report?
An Excel workbook generated by xppbp.exe that applies a set of best practice rules to a model. It is a requirement of solution certification.
How to fix "Insert method can be replaced with RecordInsertList"?
Replace insert() calls in a loop with RecordInsertList or insert_recordset. The rule does not apply to InMemory tables.
Ready to transform your D365?
Join the CIOs who develop their D365 tickets in 2–4 h for 1 credit (≈ €400 on Basic, €200 on Pro).