From 911c345245af5565a41ee5f808e8668772224a53 Mon Sep 17 00:00:00 2001 From: Hinton Date: Thu, 16 Jul 2026 10:36:49 +0200 Subject: [PATCH] docs: note ISNULL rolling-deploy pattern for JSON/TVP NOT NULL columns A NOT NULL column populated via OPENJSON or a TVP needs ISNULL(col, ) at each write site: during a rolling deployment an old server omits the field, OPENJSON/the TVP yields NULL, and the write violates the NOT NULL constraint. Scalar parameters avoid this via their own defaults; JSON/TVP fields have no per-field default. --- .claude/rules/database-dapper.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.claude/rules/database-dapper.md b/.claude/rules/database-dapper.md index b3330686c5d4..55a710b2457a 100644 --- a/.claude/rules/database-dapper.md +++ b/.claude/rules/database-dapper.md @@ -94,6 +94,16 @@ GO - Add new columns at the **end** of the column list to keep `src/Sql/dbo/` in sync. - For a `NOT NULL` column, add a `DEFAULT` constraint rather than backfilling rows: `ADD [Column] INT NOT NULL CONSTRAINT [DF_Table_Column] DEFAULT 0`. Do **not** use defaults for string columns. +- A `NOT NULL` column written through an `OPENJSON` or TVP path also needs `ISNULL([Column], )` at every + insert/update site. A scalar procedure parameter stays backwards-compatible via its own default (`@Column BIT = 0`), + but a JSON/TVP field has no per-field default — during a rolling deployment an old server omits it, `OPENJSON` + yields `NULL`, and the write violates the `NOT NULL` constraint. (The `OrganizationUser_CreateMany`, `_UpdateMany`, + and `_CreateManyWithCollectionsAndGroups` procedures are the canonical example.) + + ```sql + SELECT ..., ISNULL(OUI.[Column], 0) -- INSERT ... SELECT + SET ..., [Column] = ISNULL(OUI.[Column], 0) -- UPDATE ... SET + ``` ### Create / drop a table