Skip to content

EF Core mapping sweep: schema parity with EF Core migrations (tables + indexes) - #361

Merged
jeremydmiller merged 2 commits into
masterfrom
feat/efcore-mapping-sweep
Jul 18, 2026
Merged

EF Core mapping sweep: schema parity with EF Core migrations (tables + indexes)#361
jeremydmiller merged 2 commits into
masterfrom
feat/efcore-mapping-sweep

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

What this is

A comprehensive sweep of Weasel.EntityFrameworkCore's DbContext → Weasel table mapping. It adds a dual-schema comparison harness that, for a matrix of EF Core mapping permutations, verifies at the database-catalog level that the schema Weasel creates is identical to what EF Core's own migration system creates — and that Weasel's delta detection reports zero changes against an EF-created schema (and against its own, for idempotency). Everything the harness exposed is fixed here.

See EFCORE_IMPROVEMENTS.md (repo root) for the full write-up.

Bugs found & fixed (all pre-existing on master)

  1. PostgreSQL identifier casing — EF emits quoted PascalCase; Weasel folded to lowercase, so EF couldn't insert into Weasel-created tables. can_apply_migration_with_fk_dependencies was failing on master because of this. New opt-in ITable.PreserveIdentifierCase seam + quoted PK/FK/index DDL + case-insensitive delta matching. All-lowercase callers (Marten) emit byte-identical DDL as before.
  2. No index mapping — Weasel dropped EF's indexes. EF creates IX_* indexes for every FK by convention; Weasel's delta emitted DROP INDEX for them. New provider-neutral ITableIndex / ITable.AddIndex (all five providers); the mapper maps HasIndex (unique/composite/named/filtered/INCLUDE), conventional FK indexes, and alternate keys.
  3. HasDefaultValue literals silently dropped — now rendered via EF's own GenerateSqlLiteral (bool/string/Guid/DateTime/enum incl. conversions).
  4. Delete behaviorsClientSetNull (EF's default for optional FKs) wrongly produced ON DELETE SET NULL; all Client* behaviors now map to no action. SQL Server normalizes Restrict ≡ NO ACTION.
  5. No identity mapping — Weasel-created int-key tables broke EF inserts. ITableColumn.IsAutoNumber + PostgreSQL GENERATED BY DEFAULT AS IDENTITY emission, with correct suppression for defaults, TPT linking keys, and non-integral types.
  6. SQL Server introspection — INCLUDE columns were read as key columns (endless covering-index churn); rowversion/timestamp synonym; FK parsing left quote chars in names.

Plus mapper correctness: TPH nullability via IsColumnNullable, table-split owned entity columns, owned-with-own-table inclusion (over-fix of #234), row-internal FK skip.

Test coverage

19 comparison suites (14 PostgreSQL, 5 SQL Server): defaults, composite keys, indexes (incl. SQL Server's automatic IS NOT NULL filter on unique-nullable indexes), alternate keys, many-to-many, self-reference + delete-behavior matrix, TPH, TPT, owned entities, facets, identity strategies, rowversion, check constraints (documented gap). Known gaps are each pinned by a test or tolerated diff category.

Infrastructure

  • New ci-build-efcore.yml — the EF Core test project previously ran in no CI workflow at all (how a failing test sat on master unnoticed).
  • End-to-end tests no longer EnsureDeleted the shared marten_testing/weasel_testing databases (they were killing concurrent tests' connections with 57P01).
  • TestTfmsInParallel=false + xunit collections for schema-sharing classes and Azure SQL Edge DDL deadlocks.

Verification

  • EF suite: 56/56 (net9.0) + 58/58 (net10.0)
  • Regressions: Weasel.Postgresql.Tests 762 ✅, Weasel.SqlServer.Tests 299 ✅, Weasel.Sqlite.Tests 361 ✅, Weasel.Core.Tests 21 ✅
  • Full solution builds clean on all TFMs

🤖 Generated with Claude Code

jeremydmiller and others added 2 commits July 18, 2026 13:30
Adds a dual-schema comparison harness that verifies the tables and indexes
Weasel generates for a DbContext are identical to what EF Core's own
migration system creates (catalog-level snapshot diff on PostgreSQL and
SQL Server, plus zero-delta and idempotency assertions), and fixes every
divergence it exposed:

- PostgreSQL identifier casing: new ITable.PreserveIdentifierCase seam,
  quoted PK/FK/index DDL, and case-insensitive delta matching so EF's
  quoted PascalCase schemas round-trip (fixes the previously failing
  can_apply_migration_with_fk_dependencies)
- Index mapping: new ITableIndex + ITable.AddIndex across all five
  providers; the mapper now maps HasIndex (unique/composite/named/
  filtered/INCLUDE), EF's conventional FK indexes, and alternate keys —
  previously Weasel emitted DROP INDEX for all of them
- Literal HasDefaultValue defaults rendered via EF's own type mapping
- Client* delete behaviors no longer emit ON DELETE clauses; SQL Server
  normalizes Restrict as NO ACTION
- Identity mapping via ITableColumn.IsAutoNumber (PostgreSQL now emits
  GENERATED BY DEFAULT AS IDENTITY) with TPT/default/type suppressions
- SQL Server FetchExisting honors is_included_column; rowversion synonym;
  FK catalog parsing strips identifier quoting
- TPH nullability via IsColumnNullable, table-split owned entity columns,
  owned-with-own-table inclusion, row-internal FK skip
- New ci-build-efcore.yml (the EF test project ran in no CI workflow);
  end-to-end tests no longer EnsureDeleted the shared databases;
  TFM/schema race fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… SQL Server

On a fresh SQL Server (CI service container) several test classes raced to
CREATE DATABASE weasel_testing: one lost with "database already exists" and
others hit "cannot open database" logins against the half-created database.
Adds a shared SqlServerDatabaseBootstrap that swallows the creation race and
retries the login until the database is reachable, and serializes all
SQL Server EF test classes into one xunit collection (Azure SQL Edge also
deadlocks on concurrent DDL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit b9e82ba into master Jul 18, 2026
23 checks passed
@jeremydmiller
jeremydmiller deleted the feat/efcore-mapping-sweep branch July 18, 2026 18:46
jeremydmiller added a commit that referenced this pull request Jul 18, 2026
…d columns, index methods, drift detection, Oracle/MySql parity (#372)

Follow-ups to the EF Core schema-parity sweep (#361):

- Sequences: SequenceBase.IncrementBy (HiLo block size), Migrator.CreateSequence
  seam (PG/SS/Oracle), GetSchemaObjectsForMigration maps model sequences
  (UseHiLo/UseSequence/HasSequence) ahead of tables; CreateMigrationAsync uses it
- Check constraints: TableCheckConstraint in Weasel.Core, ITable.AddCheckConstraint,
  CREATE emission + catalog reads + conservative delta comparison on PG and SS
  (only declared checks compared; unknown constraints never dropped)
- Computed columns: ITableColumn.ComputedExpression/ComputedColumnIsStored with
  emission on PG (STORED), SS (AS ... PERSISTED), MySQL, SQLite (mapped onto its
  existing generated-column model); Oracle throws rather than emitting wrong DDL
- Index methods: ITableIndex.Method maps Npgsql HasMethod("gin") automatically
- Opt-in column drift detection: ITable.DetectColumnDrift compares canonicalized
  defaults + nullability of matching columns and emits ALTER corrections on PG/SS;
  off by default (datetime literal canonicalization is not stable enough to be safe)
- Oracle casing parity: preserve-case columns/PK/FK are quoted (folded convention
  unchanged), covered by DB-free DDL tests
- MySQL: EF CI service added, EnsureDeleted hazard removed from its e2e tests
- SQL Server FetchExisting reads column defaults/nullability; ItemDelta matching
  case-insensitive; EF docs updated (casing, sequences, drift, owned types)

Co-authored-by: Claude Fable 5 <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

Development

Successfully merging this pull request may close these issues.

1 participant