Skip to content

docs(efcore): EF Core migration generation documentation - #380

Merged
jeremydmiller merged 18 commits into
masterfrom
feat/ef-migration-docs
Jul 19, 2026
Merged

docs(efcore): EF Core migration generation documentation#380
jeremydmiller merged 18 commits into
masterfrom
feat/ef-migration-docs

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #370 — the final phase of the EF Core migration generation epic (#371). Stacked on #379#378#377#376#375; merge in order. Once this lands, every phase of #371 is complete.

New pages (under docs/efcore/)

  • migration-generation.md — the overview and getting-started guide: what the feature is and a side-by-side of when to use Weasel-native db-patch/db-apply vs. db-ef-migration; the three generated artifacts (attribute-only migration classes, stub context with relocated history table + design-time factory, JSON schema snapshot) and what each is for; the db-ef-migration adddotnet ef database update walkthrough including idempotent scripts and bundles; incremental migrations and the in-memory snapshot diff; the --against-database live-baseline mode; adopting existing databases via db-ef-migration baseline; the translation-layer API; and the full limitations & raw-SQL fallbacks story (partitioning, functions, expression indexes, the SQL Server unique-index filter behavior, no dotnet ef migrations add/remove against the stub, the SQLite exclusion per the Spike: attribute-only EF Core migrations without a real EF model #364 spike, and why the PendingModelChangesWarning suppression exists).
  • migration-coexistence.md — mixed EF Core + Marten/Polecat/Wolverine applications: the two-migration-streams model, the relocated __EFMigrationsHistory, --context usage, the single-owner rule with ExcludeFromMigrations, the EF-projection round-trip ownership guidance, multi-database and multi-tenant notes, and how the dual + inverted harnesses verify all of it.

Updates to existing docs

  • docs/efcore/migrations.md now opens by positioning the two directions side by side, per the issue.
  • VitePress nav gains both new pages.
  • CLAUDE.md project structure now includes Weasel.EntityFrameworkCore; EFCORE_IMPROVEMENTS.md records the epic's capability set.
  • Computed-column documentation for the PostgreSQL/SQL Server table-modeling pages shipped with feat: computed-column introspection + delta detection (PostgreSQL + SQL Server) #373 (the computed-column PR), as the issue requested.

All code samples are mdsnippets sourced from the new compilable DocSamples/EfCoreMigrationSamples.cs, per repo convention — no hand-typed samples. DocSamples builds clean and mdsnippets has been run (expanded snippets are checked in).

Downstream consumption pages for Marten/Polecat/Wolverine are tracked in their own repos per the issue.

🤖 Generated with Claude Code

jeremydmiller and others added 6 commits July 18, 2026 16:10
Closes #365. First implementation phase of the EF Core migration
generation epic (#371), building on the #364 spike results.

- New MigrationOperationTranslation in Weasel.EntityFrameworkCore: walks
  the provider-neutral surface (ITable/ITableColumn/ITableIndex/
  ForeignKeyBase/SequenceBase) and produces EF Core MigrationOperation
  instances — the reverse of MapToTable. Raw store type strings
  (ColumnType) everywhere so EF's CLR mapping is bypassed and DDL matches
  Weasel exactly; the CLR type is a best-effort inverse used only for the
  Column<T>() generic in emitted C#
- CreateTable with nested columns / primary key / check constraints /
  foreign keys, one CreateIndex per index, EnsureSchema per non-default
  schema (deduplicated; default public/dbo emitted as null Schema like
  EF's own scaffolding), CreateSequence from SequenceBase
- Provider specifics: identity → Npgsql:ValueGenerationStrategy or
  SqlServer:Identity annotations; computed columns → ComputedColumnSql +
  IsStored (always stored on PG); index includes/method annotations;
  CascadeAction → ReferentialAction with SQL Server Restrict ≡ NoAction
  mirroring mapDeleteBehavior
- Raw-SQL fallback: non-table/non-sequence objects (functions, sprocs,
  table types) and anything matched by the ForceRawSql hook (e.g.
  partitioned tables) are wrapped in SqlOperation carrying the object's
  own WriteCreateStatement DDL; expression indexes throw with guidance
  to use the hook
- ToDropMigrationOperations for Down() bodies: reverse-order DropTable /
  DropSequence / raw drops; schemas never dropped (may be shared with
  Marten/Wolverine)
- Weasel.Core additions: ITable.Columns and ITableIndex.Columns expose
  the column collections on the neutral surface (implicitly satisfied by
  every provider's concrete types)

13 new DB-free unit tests; all provider suites green locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #366. Second implementation phase of the EF Core migration
generation epic (#371), rendering the #365 operation lists into
compilable attribute-only migration files.

- EfMigrationFileEmitter.EmitMigration renders Up/Down operation lists
  over the stable public MigrationBuilder surface — deliberately not
  EF's pubternal CSharpMigrationsGenerator (dotnet/efcore#23595).
  Generated migrations carry [DbContext]/[Migration] attributes and no
  BuildTargetModel body, per the #364 spike verification
- Renders EnsureSchema, CreateTable (nested columns with raw store
  types, PK incl. composite, check constraints, FKs with referential
  actions), CreateIndex (unique/filter + annotations), CreateSequence,
  Sql (verbatim strings), DropTable, DropSequence; the
  Npgsql:ValueGenerationStrategy annotation is rendered as the real
  NpgsqlValueGenerationStrategy enum literal with the using added on
  demand; unknown operations/annotations throw rather than emitting
  wrong code
- Column names map to anonymous-type members with @-escaping for
  reserved words and name:-argument fallback for non-identifier names
- Migration ids are yyyyMMddHHmmss_Name UTC with a monotonicity guard:
  LastMigrationId bumps the timestamp until the new id sorts strictly
  after (EF orders by plain string sort)
- EmitStubContext generates the no-entity host context: provider
  configured, history table relocated into the critter-stack schema,
  EF 9+ PendingModelChangesWarning suppressed, registration snippet in
  the XML docs, plus an IDesignTimeDbContextFactory reading
  WEASEL_EF_CONNECTION so dotnet ef update/script/bundle work without
  an application host

Testing: the generated sample files (from a Weasel schema with sequence,
identity, checks, FK, filtered index) are CHECKED IN and compiled as part
of the test project — the "generated files compile" acceptance — with a
drift-guard test proving they are byte-for-byte emitter output, and an
end-to-end test applying them through the real EF runtime against
PostgreSQL, round-tripping the schema against Weasel's own delta
detection (no changes), and migrating back down to zero.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #367. Third implementation phase of the EF Core migration
generation epic (#371).

- EfSchemaSnapshot: JSON-serialized design-time snapshot of the Weasel
  typed model (tables/columns/indexes/FKs/checks/PK, sequences, and
  raw-SQL objects captured as their CREATE/DROP DDL) — Weasel's analog
  of EF's ModelSnapshot, written beside the generated migrations and
  never compiled. The Sable lesson without the shadow database
- The snapshot DTOs are now the canonical IR: the #365 ITable
  translation routes through SnapshotTable.From(...) into shared
  operation builders, so first-migration translation and the
  incremental differ can never drift apart
- EfSnapshotDiffer.Diff(baseline, target): in-memory diff producing
  incremental Up/Down operations — Add/Alter/DropColumn (Alter carries
  the old definition), Create/DropIndex (recreate on change),
  Add/DropForeignKey, Add/DropCheckConstraint, Drop+AddPrimaryKey,
  Create/Drop/AlterSequence, EnsureSchema for new schemas (never
  dropped), and raw-object add/remove via Sql(). Down runs in reverse
  order of Up. Changed raw-SQL objects are refused with guidance —
  the snapshot diff cannot infer a safe transform for partitioned
  tables or function bodies
- EfSnapshotDiffer.DiffAgainstDatabaseAsync: the live-database baseline
  mode — Weasel's own CreateMigrationAsync SQL (updates + rollbacks)
  wrapped in Sql() operations, covering everything the snapshot diff
  refuses (partition additive/rebuild, function changes)
- Emitter renders the incremental operations: AddColumn/AlterColumn/
  DropColumn, DropIndex, AddForeignKey/DropForeignKey standalone,
  Add/DropPrimaryKey, Add/DropCheckConstraint, AlterSequence

Renames are deliberately not inferred (the model carries no rename
intent); the seam arrives with the CLI phase where renames can be
declared explicitly.

Tests: snapshot JSON round-trip yields a zero diff; add-column /
changed-index / new-table+FK / altered-column scenarios; changed raw
object refusal; incremental ops render through the emitter with the id
monotonicity guard; and an end-to-end acceptance test that applies the
initial generated migration via EF, diffs a changed model against the
snapshot, executes the incremental operations through the real Npgsql
migrations SQL generator, has Weasel's own delta detection report None,
then rolls back down and round-trips again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #368. Fourth implementation phase of the EF Core migration
generation epic (#371).

- New db-ef-migration JasperFx command in Weasel.EntityFrameworkCore
  (discovered via [assembly: JasperFxAssembly]; Weasel.Core stays
  EF-free), using the same WeaselInput / TryChooseSingleDatabase
  database-selection machinery as db-patch — IDatabase is the single
  source of schema objects, so Marten/Wolverine/Polecat tables all
  flow in through one door
- `add <Name>`: first run scaffolds the stub context (history table
  relocated into the first non-default schema of the database's
  objects), the initial create-everything migration, and the JSON
  snapshot; later runs diff against the snapshot (or the live database
  with --against-database) and emit an incremental migration with the
  id monotonicity guard. --output/--namespace/--context/
  --history-schema flags
- `script`: documents the canonical EF toolchain path (dotnet ef
  migrations script --idempotent / bundle) verified by the #364 spike —
  idempotent scripting needs the compiled migrations, which only exist
  in the consuming project
- `baseline`: adopts a pre-existing database by inserting
  __EFMigrationsHistory rows (create-if-missing relocated history
  table) for every generated migration file without executing them —
  the EF-sanctioned baselining technique, idempotent across runs
- EfMigrationGenerator is the testable engine behind the command:
  provider detection from the Migrator type, structural partition
  detection as the default ForceRawSql routing (no provider
  references), connection resolution via IConnectionSource

Tests: provider detection, partition detection, first-run scaffold →
no-change no-op → model change → incremental add with ordered ids, and
baselining against live PostgreSQL (rows recorded once, idempotent
second pass, verified in the relocated history table).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #369. Fifth implementation phase of the EF Core migration
generation epic (#371).

- InvertedComparisonHarness: the reverse of SchemaComparisonHarness —
  schemas defined as Weasel objects, the generated migration chain
  (initial + snapshot-diffed incrementals) COMPILED WITH ROSLYN and
  applied through the real EF runtime via Migrate(), then validated by
  (a) catalog-level SchemaComparer parity against a Weasel-created
  schema using the existing neutral introspectors and (b) Weasel's own
  SchemaMigration.DetermineAsync reporting None against the EF-migrated
  database. PostgreSQL and SQL Server variants
- Scenarios: baseline conventions (identity, defaults, varchar facets,
  unique+filtered index, FK cascade, check constraint), computed
  columns, raw-SQL fallback objects (list-partitioned table + plpgsql
  function via Sql() blocks + sequence), a two-migration incremental
  chain (add column + index + new table), coexistence of two generated
  migration sets with separate schemas/history tables in one database,
  and a SQL Server baseline
- Two generator fixes surfaced by the harness:
  - generated migration files now emit `using System;` (they must be
    self-contained rather than relying on ImplicitUsings)
  - SQL Server unique indexes without an explicit predicate are emitted
    as raw CREATE UNIQUE INDEX DDL — EF's SqlServer generator
    auto-appends a WHERE col IS NOT NULL filter whenever the (empty)
    target model cannot prove the columns non-nullable, which would
    diverge from Weasel's index
- CI: the new suites live in Weasel.EntityFrameworkCore.Tests, which
  ci-build-efcore.yml already runs against PostgreSQL + SQL Server on
  net9.0/net10.0 — no workflow change needed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #370. Final phase of the EF Core migration generation epic (#371).

- New docs/efcore/migration-generation.md: overview + when to use which
  flow (Weasel-native vs EF-artifact generation), the three generated
  artifacts, getting-started walkthrough (db-ef-migration add ->
  dotnet ef database update), incremental migrations + snapshot,
  live-database baseline mode, adopting existing databases via
  baseline, translation-layer API, and the limitations / raw-SQL
  fallback boundaries (partitioning, functions, expression indexes,
  SS unique-index filter behavior, no ef migrations add/remove against
  the stub, SQLite exclusion, PendingModelChangesWarning explanation)
- New docs/efcore/migration-coexistence.md: mixed EF + Marten/
  Wolverine/Polecat apps — two migration streams, relocated history
  table, --context usage, the single-owner rule with
  ExcludeFromMigrations, EF projection round-trip ownership guidance,
  and how the harnesses verify coexistence
- docs/efcore/migrations.md now positions the two directions side by
  side; VitePress nav updated
- All code samples are mdsnippets sourced from the new compilable
  DocSamples/EfCoreMigrationSamples.cs per repo convention
- CLAUDE.md project structure + EFCORE_IMPROVEMENTS.md capability
  record updated

Computed-column docs for the PG/SS table-modeling pages shipped with
the computed-column PR (#373).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller and others added 11 commits July 18, 2026 17:36
…ilePath

Deterministic CI builds rewrite [CallerFilePath] to the virtual /_/ source
root, which does not exist on disk — the drift-guard tests failed on CI
with an IO error. Walk up from AppContext.BaseDirectory to the repo root
instead.

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

# Conflicts:
#	src/Weasel.EntityFrameworkCore/EfMigrationFileEmitter.cs
#	src/Weasel.EntityFrameworkCore/MigrationOperationTranslation.cs
…rness

# Conflicts:
#	src/Weasel.EntityFrameworkCore/MigrationOperationTranslation.cs
Base automatically changed from feat/ef-inverted-harness to master July 19, 2026 01:11
@jeremydmiller
jeremydmiller changed the base branch from feat/ef-inverted-harness to master July 19, 2026 01:11
@jeremydmiller
jeremydmiller merged commit 0ed4d69 into master Jul 19, 2026
23 checks passed
@jeremydmiller
jeremydmiller deleted the feat/ef-migration-docs branch July 19, 2026 01:17
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.

EF migration generation: documentation

1 participant