Add an events article with compiled good/bad samples: GetDatabaseTableTriggerSetup (Global Triggers) and OnAfterGetDatabaseTableTriggerSetup (GlobalTriggerManagement) share four var Booleans across all subscribers, which run in no particular order. Assigning false, or a lookup result without or-ing in the current value, clears flags other features set (Dataverse sync, API webhooks, data archive, and, for direct Global Triggers subscribers, the change log). Recommends the codeunit 49 integration events per Learn's guidance on system codeunits 2000000001..2000000010 and handler-side table filtering. Wired into al-events-review tokens and an event-design check, with carve-outs for conditional := true, Flag := Flag or ..., and the no-op "if not Flag then Flag := false" found in BCApps. Registered in the events review-fixtures override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.7 KiB
| bc-version | domain | keywords | technologies | countries | application-area | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
events |
|
|
|
|
Database trigger setup flags may only be set to true
Description
The OnDatabaseInsert, OnDatabaseModify, OnDatabaseDelete, and OnDatabaseRename events let one subscriber react to writes on any table, receiving the record as a RecordRef. They are opt-in per table: before raising them, the platform raises GetDatabaseTableTriggerSetup(TableId; var OnDatabaseInsert; var OnDatabaseModify; var OnDatabaseDelete; var OnDatabaseRename) on the system codeunit Global Triggers (2000000002), and every interested feature turns on the flags it needs for that table. Codeunit 49 GlobalTriggerManagement subscribes to it, collects the Dataverse integration and API webhook flags, raises its own integration event OnAfterGetDatabaseTableTriggerSetup with the same four var Booleans, and then adds the change log flags.
The four Booleans are shared by every subscriber in the chain, and subscribers run in no particular order. A subscriber that assigns false, or assigns an expression that can be false such as OnDatabaseModify := MySetup.Get(TableId), overwrites what another feature already set for that table. The table then stops raising the database events, and features that rely on them (the change log, Dataverse synchronization, API webhook notifications, data archiving) stop working for it with no error. GlobalTriggerManagement asks the change log last in the normal execution context, and its comment says it does not want anyone to disable change log management. That ordering protects only the change log flags, and only against OnAfterGetDatabaseTableTriggerSetup subscribers. It does not protect the other features' flags, and it does not protect anything against another direct subscriber to Global Triggers.
Best Practice
Subscribe to GlobalTriggerManagement's integration events, OnAfterGetDatabaseTableTriggerSetup to opt in and OnAfterOnDatabaseInsert, OnAfterOnDatabaseModify, OnAfterOnDatabaseDelete, or OnAfterOnDatabaseRename to react. Microsoft Learn does not recommend subscribing directly to the events of system codeunits 2000000001..2000000010. Some Microsoft apps do, for example Data Archive Db Subscriber, and those subscriptions still compile and run.
In the setup subscriber, only turn on flags: if IsTracked(TableId) then OnDatabaseModify := true;, or OnDatabaseModify := OnDatabaseModify or IsTracked(TableId); as Change Log Management does. Turn on only the operations and tables the feature needs. In the handler, check RecRef.Number against the feature's own setup and skip temporary records, because the events also fire for every table another feature opted in. A statement such as if not OnDatabaseDelete then OnDatabaseDelete := false;, which appears in BCApps, cannot clear a flag and is not this anti-pattern.
See sample: database-trigger-setup-flags-may-only-be-set-to-true.good.al.
Anti Pattern
In a subscriber to GetDatabaseTableTriggerSetup (Global Triggers) or OnAfterGetDatabaseTableTriggerSetup (GlobalTriggerManagement), any assignment to one of the four var flags that can store false when the flag was already true. This includes a literal false, an assignment from a lookup or Boolean expression without or on the flag's current value, and Clear on the parameter.
A second, weaker signal: a subscriber to OnDatabaseInsert/Modify/Delete/Rename or to OnAfterOnDatabase* when the app has no setup subscriber that turns on the matching flag. The handler then runs only for tables that some other feature happened to opt in. Check the whole app before flagging this, because the opt-in can live in a different codeunit than the handler.
See sample: database-trigger-setup-flags-may-only-be-set-to-true.bad.al.
References
- Transitioning from codeunit 1 to system codeunits:
GetDatabaseTableTriggerSetupandOnDatabase*moved to codeunit 49GlobalTriggerManagement. It also advises against subscribing directly to system codeunits 2000000001..2000000010 and recommends the integration events instead. - Event types, global events: the codeunit 49 integration events. Subscribing to events: subscribers run one at a time in no particular order.
Global Triggers(2000000002) in the System symbols:GetDatabaseTableTriggerSetupwith fourvar Booleanparameters, andOnDatabaseInsert/Modify/Delete(RecRef)andOnDatabaseRename(RecRef, xRecRef).- GlobalTriggerManagement.Codeunit.al: setup subscriber and change log comment (lines 51-65),
OnAfterGetDatabaseTableTriggerSetup(173-174),OnAfterOnDatabase*(178-194). - Only-true assignments in BCApps:
ChangeLogManagement.Codeunit.allines 85-88 (or),APIWebhookNotificationMgt.Codeunit.al224-227,CRMIntegrationManagement.Codeunit.al3748-3753,MasterDataManagement.Codeunit.al1511-1516, andDataArchiveDbSubscriber.Codeunit.al27-32 (turns on onlyOnDatabaseDelete, and skips temporary records in its handler).