From b04e37c807b488b506cd885359f6d17cbf5d0748 Mon Sep 17 00:00:00 2001 From: Michael Dieringer <65093775+MichaelDieringer@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:09:12 +0200 Subject: [PATCH] =?UTF-8?q?Foresl=C3=A5=20regel:=20Never=20change=20a=20pu?= =?UTF-8?q?blished=20table's=20primary/clustered=20key=20field=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../do-not-change-primary-key-field-list.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 custom/knowledge/architecture/do-not-change-primary-key-field-list.md diff --git a/custom/knowledge/architecture/do-not-change-primary-key-field-list.md b/custom/knowledge/architecture/do-not-change-primary-key-field-list.md new file mode 100644 index 0000000..b6b3af5 --- /dev/null +++ b/custom/knowledge/architecture/do-not-change-primary-key-field-list.md @@ -0,0 +1,81 @@ +--- +bc-version: [all] +domain: architecture +keywords: [primary-key, clustered-key, table-design, appsource, breaking-change, schema-upgrade] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Never change a published table's primary/clustered key field list + +## Description + +Once a table has shipped — to AppSource, or to any customer who has upgraded +onto it — its primary key (and any other `Clustered = true` key) is frozen. +This includes adding a field to the key, not only removing or reordering +one: Business Central identifies existing rows by their key value, and any +change to which fields compose that key invalidates every row already +stored under the old key shape. The platform's own upgrade validation +rejects this outright (`AS0009`), and it fails identically whether the +target is a real AppSource submission or a plain extension upgrade on any +environment that already has the table installed with data. + +This is easy to trip over because it doesn't look like the well-known +"don't delete a field" mistake: the field being added to the key is often +brand new, and the temptation is to fold a new discriminating dimension +(a type, a flow, a category) straight into the existing key because that is +the natural, un-denormalized way to model it. On an unpublished table that +is correct. On a published one, it is a breaking schema change regardless +of which direction the field list changed. + +## Why + +A key is not just an index — it is the table's identity contract with every +row already stored under it, and with every dependent extension that reads +or writes through `Get()` calls shaped by that key. Changing its field list +means BC can no longer say "this stored row is the same logical record it +was before the upgrade," so it refuses the upgrade rather than silently +losing or duplicating data. There is no in-place fix once this state is +reached — the only way out is reverting the key to its published shape. + +## Anti Pattern + + // Table already shipped with: + // key(PK; "Period Start") { Clustered = true; } + // + // "Just add a field to distinguish the two flows" — + field(2; Flow; Enum "Some Flow Enum") { } + keys + { + key(PK; Flow, "Period Start") { Clustered = true; } + } + // AS0009 on next AppSource validation or extension upgrade: + // "Ændring af felter for nøglen 'PK' er ikke tilladt." + +## Best Practice + + // Leave the published table's key exactly as shipped: + // key(PK; "Period Start") { Clustered = true; } + // + // Model the new dimension as a new, separate table instead — + // mirroring the published one's shape, with its own key: + table 50101 "New Flow Stats" + { + keys + { + key(PK; "Period Start") { Clustered = true; } + } + } + // The published table's schema and public procedures never change. + // Orchestration code branches by flow to the matching table instead + // of filtering one shared table by an extra key field. + +## Scope + +All CURABIS apps once a table has been published — AppSource apps from +their first submission onward, customer apps from their first +customer-environment deployment onward. Does not apply to a table +introduced in the same, still-unreleased change set — a genuinely new table +can define its key however it needs to; the constraint begins at +publication, not at table creation.