Skip to content

Fix EF Core schema mapping for IsRowVersion()/xmin (#290) and ComplexProperty/ComplexCollection ToJson (#291)#295

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/290-291-efcore-systemcolumn-and-complex-json
May 24, 2026
Merged

Fix EF Core schema mapping for IsRowVersion()/xmin (#290) and ComplexProperty/ComplexCollection ToJson (#291)#295
jeremydmiller merged 1 commit into
masterfrom
fix/290-291-efcore-systemcolumn-and-complex-json

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

@jeremydmiller jeremydmiller commented May 24, 2026

Two related gaps in Weasel.EntityFrameworkCore's metadata → table mapping (DbContextExtensions.MapToTable), both reported on EF Core 9/10 + Npgsql. Closes #290 and #291.

#290 — Npgsql IsRowVersion()xmin

The Npgsql-recommended optimistic-concurrency idiom maps a uint token (Property(x => x.Version).IsRowVersion()) to PostgreSQL's implicit xmin system column, not a new column. The mapper emitted xmin as a real column, so migration failed:

42701: column name "xmin" conflicts with a system column name

Fix: new provider hook Migrator.IsSystemColumn(string) (default false), overridden in PostgresqlMigrator to recognise PG's reserved system columns — tableoid, xmin, cmin, xmax, cmax, ctid. (oid is intentionally excluded — it's a valid user column on PG 12+.) MapToTable's column loop skips any property whose resolved column name is a system column.

#291ComplexProperty(...).ToJson() / ComplexCollection(...).ToJson()

OwnsOne(...).ToJson() worked (PR #233) because owned entities surface as navigations to a JSON-mapped entity type. EF Core 10's complex properties/collections are not navigations and have no separate entity type — their JSON container lives on the IComplexType — so they were silently skipped and the first insert failed with 42703: column "X" does not exist.

Fix: new mapComplexJsonColumns iterating entityType.GetComplexProperties(), emitting one container column per top-level JSON-mapped complex type (GetContainerColumnName/GetContainerColumnType, default jsonb; nullability from IComplexProperty.IsNullable). Both the single and collection shapes serialize into one container column. Gated #if NET10_0_OR_GREATER — complex-type JSON mapping is an EF Core 10 feature, so the net9.0 target is unaffected.

Both fixes stay in the provider-neutral mapper + the Migrator abstraction; no PG-specific reference leaks into Weasel.EntityFrameworkCore.

Answering the issues' questions

Test plan

  • PostgresqlMigratorSystemColumnTests — 11 assertions on IsSystemColumn (system columns incl. case-insensitive; ordinary columns incl. oid)
  • row_version_system_column — confirms Npgsql maps Versionxmin, then that MapToTable omits xmin while keeping id/name
  • complex_json_columns (net10.0) — ComplexProperty/ComplexCollection .ToJson() each produce a jsonb container column
  • Existing OwnsOne().ToJson() mapping tests unchanged (2/2)
  • Model-mapping tests build the EF model offline (no live DB); ran green against PG locally
  • Weasel.Core / PostgreSQL / EFCore build clean; AOT smoke gate green

🤖 Generated with Claude Code


Closes #290.
Closes #291.

…Property/ComplexCollection ToJson (#291)

Two gaps in Weasel.EntityFrameworkCore's metadata → table mapping
(DbContextExtensions.MapToTable), both surfacing as failed/incomplete
migrations on EF Core 9/10 + Npgsql.

## #290 — Npgsql IsRowVersion() → xmin

EF maps an Npgsql uint concurrency token (Property(x => x.Version)
.IsRowVersion()) to PostgreSQL's implicit "xmin" system column rather than
a new column. The mapper emitted it as a real column, so migration failed
with "42701: column name 'xmin' conflicts with a system column name".

Fix: add a provider hook Migrator.IsSystemColumn(string) (default false),
overridden in PostgresqlMigrator to recognize PG's reserved system columns
(tableoid/xmin/cmin/xmax/cmax/ctid — oid is excluded, it's a valid user
column on PG 12+). MapToTable's column loop skips any property whose
resolved column name is a system column.

## #291 — ComplexProperty/ComplexCollection .ToJson()

OwnsOne(...).ToJson() worked (PR #233) because owned entities surface as
navigations to a JSON-mapped entity type. EF Core 10's complex properties /
collections are not navigations and carry no separate entity type — their
JSON container lives on the IComplexType — so they were silently skipped,
and the first insert failed with 42703 (column does not exist).

Fix: add mapComplexJsonColumns, iterating entityType.GetComplexProperties()
and emitting one container column per top-level JSON-mapped complex type
(GetContainerColumnName / GetContainerColumnType, default jsonb; nullability
from IComplexProperty.IsNullable). Both single (ComplexProperty) and
collection (ComplexCollection) shapes serialize into a single container
column. Gated #if NET10_0_OR_GREATER since complex-type JSON mapping is an
EF Core 10 feature; the net9.0 target is unaffected.

## Tests

- PostgresqlMigratorSystemColumnTests — 11 assertions on IsSystemColumn
  (system columns incl. case-insensitive; ordinary columns incl. oid).
- row_version_system_column — confirms Npgsql still maps Version→xmin, then
  that MapToTable omits xmin while keeping id/name.
- complex_json_columns (NET10) — ComplexProperty/ComplexCollection .ToJson()
  each produce a jsonb container column.
  Model-mapping tests; build the EF model offline, no live DB needed.

Existing OwnsOne().ToJson() mapping tests unchanged (2/2). Weasel.Core,
PostgreSQL and EFCore build clean; AOT smoke gate stays green (IsSystemColumn
is a trivial virtual, no reflection).

Closes #290. Closes #291.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 6d450d6 into master May 24, 2026
21 checks passed
@jeremydmiller jeremydmiller deleted the fix/290-291-efcore-systemcolumn-and-complex-json branch May 24, 2026 01:18
jeremydmiller added a commit that referenced this pull request May 24, 2026
First patch release on the 9.0 line. Ships:
- #294 (closes #293) — retry transient catalog-concurrency errors (XX000
  "tuple concurrently updated", 40001, 40P01) in PostgreSQL migration DDL
- #295 (closes #290, #291) — EF Core schema mapping: skip Npgsql
  IsRowVersion()->xmin system column; emit ComplexProperty/ComplexCollection
  .ToJson() container columns (EF Core 10)
- #296 — JasperFx 2.0.0 -> 2.0.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant