bcquality/microsoft/knowledge/upgrade/no-changecompany-in-upgrade.md
Jesper Schulz-Wedde 4e33ca87e2 knowledge(upgrade): upgrade code must not use ChangeCompany
Addresses ADO bug 651092. Upgrade and feature data update code must run in the
context of the company being upgraded; ChangeCompany leaves triggers, events and
upgrade tags in the calling company and races the target company's own upgrade.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-02 10:11:49 +02:00

4.4 KiB

bc-version domain keywords technologies countries application-area
all
upgrade
changecompany
cross-company
onupgradepercompany
onupgradeperdatabase
feature-data-update
taskscheduler
multi-company
company-context
al
w1
all

Upgrade code must not use ChangeCompany

Description

The platform upgrades each company in its own context: OnUpgradePerCompany (like the other PerCompany upgrade and install triggers) runs once per company, in a separate system session opened for that company, while PerDatabase triggers run in a session that opens no company. Calling ChangeCompany(<name>) in upgrade code reaches from the company being upgraded into another company. That company has its own upgrade session, so the work is either duplicated or racing it. The platform does not document an order between companies, so a read can see data in the pre-upgrade or post-upgrade shape. Per-company upgrade tags are set for the current company only, so a tag set after writing to company B is recorded for company A, and B's own session runs the migration again. Execution context does not move with ChangeCompany: table triggers and trigger-event subscribers still run in the calling company (see changecompany-runs-triggers-in-the-calling-company). Data and side effects then land in different companies, and an error in another company's data aborts the current company's upgrade. The same applies to feature-switch data updates, the UpdateData implementation of the Feature Data Update interface. Feature Management schedules that update as one task per company.

Best Practice

Upgrade code operates only on the company it is running in. Put per-company data migration in OnUpgradePerCompany (or a helper reachable only from it), guard it with a per-company upgrade tag, and let the platform invoke it for every company. Reserve OnUpgradePerDatabase for tables with DataPerCompany = false and other database-wide state; it needs no ChangeCompany. A feature data update implements UpdateData against the current company only. When code outside the upgrade pipeline must start work in other companies, schedule it in each company with TaskScheduler.CreateTask and the company name, as Feature Management does, rather than writing there through ChangeCompany. The scheduled task runs in the target company, so triggers, events, permissions, and upgrade tags all use that company.

See sample: no-changecompany-in-upgrade.good.al.

Anti Pattern

A loop over the Company table in OnUpgradePerDatabase or OnUpgradePerCompany that calls ChangeCompany(Company.Name) on a record or RecordRef and then reads, inserts, modifies, or deletes data. Another form is a Feature Data Update implementation that does the same to update every company from one task. Reads are not exempt: they observe another company whose upgrade state is unknown.

Detection signal: Record.ChangeCompany(<name>) or RecordRef.ChangeCompany(<name>) with a company-name argument in a codeunit with Subtype = Upgrade, in any procedure transitively reachable from its upgrade triggers, or in a codeunit implementing the Feature Data Update interface. Do not flag the parameterless ChangeCompany(), which only points the variable back at the current company, or ChangeCompany in ordinary runtime code; changecompany-runs-triggers-in-the-calling-company governs that.

See sample: no-changecompany-in-upgrade.bad.al.

References