Three Microsoft-layer articles with good/bad samples: - error-handling/declined-confirm-must-abort-not-partially-apply - data-modeling/delete-master-data-with-trigger - data-modeling/master-data-must-be-inserted-with-trigger Wire targeted worklist cues into al-error-handling-review and al-data-modeling-review and register the samples in review-fixtures.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 KiB
| bc-version | domain | keywords | technologies | countries | application-area | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
data-modeling |
|
|
|
|
Create master records with Insert(true) unless the caller does the trigger's work itself
Description
Record.Insert() does not run OnInsert: RunTrigger defaults to false. On a standard master table that trigger initializes the record. Customer.OnInsert assigns No. and No. Series from Sales & Receivables Setup when No. is blank, then defaults Invoice Disc. Code and the salesperson, creates the contact, syncs Global Dimension 1/2 Code from existing Default Dimension rows (DimMgt.UpdateDefaultDim creates no default dimensions), calls UpdateReferencedIds, and sets the last-modified timestamp. Item.OnInsert assigns No., No. Series, and Costing Method when No. is blank and always runs the dimension sync and UpdateReferencedIds.
A bare Insert() produces a row that looks complete but lacks what downstream code assumes: with a blank No. the key stays blank; with a supplied No. the contact, defaults, and timestamps are silently missing. This is the caller-side counterpart of master-table-no-from-number-series-in-oninsert: that design only works when callers run the trigger.
Best Practice
When code creates a record in Customer, Vendor, Item, G/L Account, Contact, or a custom master with initializing OnInsert logic, call Insert(true), then validate fields and Modify(true). Importing from an external source is not an exception: BCApps' data-migration facades (CustomerDataMigrationFacade, ItemDataMigrationFacade, GLAccDataMigrationFacade) use Insert(true). This is the "trigger does work the caller depends on" case of pass-false-to-insert-when-trigger-not-needed; the decision stays per call.
Legitimate Insert() calls, not in scope: temporary records and buffer or staging tables; a caller that visibly assigns what the trigger would and then applies a template (CatalogItemManagement.CreateNewItem sets No. and Costing Method before Item.Insert()); and an XMLport that round-trips complete rows exported from Business Central (ExportItemData). Modify() without the trigger is routine on masters for technical fields and is not covered. Upgrade code that bypasses triggers is covered by datatransfer-skips-triggers-and-subscribers.
See sample: master-data-must-be-inserted-with-trigger.good.al.
Anti Pattern
Code creates a non-temporary master record with Init, field assignments or Validate calls, and Insert()/Insert(false), without itself assigning the number and the other fields OnInsert would set.
See sample: master-data-must-be-inserted-with-trigger.bad.al.
References
- Record.Insert(Boolean) method: "If this parameter is false, the code in the OnInsert trigger is not executed. The default value is false."
- BCApps
src/Layers/W1/BaseApp/Sales/Customer/Customer.Table.al,OnInsert, lines 2432-2472;src/Layers/W1/BaseApp/Inventory/Item/Item.Table.al,OnInsert, from line 2587. - BCApps
src/Layers/W1/BaseApp/Finance/Dimension/DimensionManagement.Codeunit.al,UpdateDefaultDim, lines 894-913. - BCApps
src/Layers/W1/BaseApp/Inventory/Item/Catalog/CatalogItemManagement.Codeunit.al,CreateNewItem, lines 545-565;src/Layers/W1/BaseApp/Inventory/Item/ExportItemData.XmlPort.al, line 405. - BCApps
src/Layers/W1/BaseApp/System/DataMigration/:CustomerDataMigrationFacade.Codeunit.alline 67,ItemDataMigrationFacade.Codeunit.alline 76,GLAccDataMigrationFacade.Codeunit.alline 77.