Skip to content

5.1.0: pending-changes visibility, runtime differ, JSON container indexes, Npgsql index options - #28

Merged
CaffeinatedCoder merged 10 commits into
mainfrom
release/5.1.0
Sep 5, 2026
Merged

CaffeinatedCoder merged 10 commits into
mainfrom
release/5.1.0

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Scope

One release PR, one commit per concern. Every concern touches the changelog, which the version gate ties to the bump in the first commit, so the concerns were not opened as separate PRs. Each commit is independently reviewable; say so if you want them split.

Fixed

  • HasDifferences never saw this package. EF's MigrationsModelDiffer.HasDifferences runs the protected Diff, not the GetDifferences we override, so dotnet ef migrations has-pending-model-changes, the pending-model-changes warning in Migrate(), and the snapshot check in migrations remove all reported "no changes" for a complex-index-only change. Three-line override in core. A CI gate built on has-pending-model-changes may now fail where it passed before — that is the gate working.
  • Complex index named like a native HasIndex on the same table scaffolded two CREATE INDEX statements under one name and failed at apply (42P07). Rejected at migrations add; the native→complex handover under one name still diffs.
  • Property-level options from the other satellite were dropped by the whitelist without a word (a property-level .UseGin() diffed by SQL Server scaffolded a plain B-tree). Each satellite forwards the other provider's prefix only so the existing validation rejects it; the PostgreSQL differ gains the SqlServer:* rejection it lacked for either path.

New

  • Runtime registration of the differ: UseComplexIndexes() (core), UseNpgsqlComplexIndexes() now also registers the differ, UseSqlServerComplexIndexes() (new), plus Add…ComplexIndexes() for internal service providers. EnsureCreated(), GenerateCreateScript() and Migrate()'s pending-changes check run the runtime differ, which the design-time wiring never reaches. Closes the EnsureCreated half of [Bug] Incorrect migration is generated #4.
  • Whole-document JSON indexes (PostgreSQL): HasComplexIndex(x => x.Payload, ix => ix.UseGin().HasOperators("jsonb_path_ops")) on a ToJson() property or a complex collection indexes the jsonb container column through the stock generator. Nested sub-documents resolve to a -> extraction.
  • HasStorageParameter(name, value) and UseCollation(params string[]) on PostgreSQL indexes. Collation needed a small core seam (ToOperationAnnotationName): the option is stored under Npgsql's model key because Relational:Collation on a property is the column's collation, and mapped to the operation key Npgsql's generator reads.
  • HasComplexIndex on the non-generic ComplexTypePropertyBuilder, so c.Property("Value") can carry one.

Changed

  • Declarations on an entity type with no table (the abstract base of a TPC hierarchy) fail at migrations add instead of producing nothing. View- and query-mapped types are still skipped.

Deliberately out

IsTsVectorExpressionIndex (Npgsql's generator needs the index in the model), typed filter predicates (5.2 candidate), snapshot-format and TPC-proper changes (major).

Verification

  • 253 tests, 0 skipped — the integration class ran against a live PostgreSQL 18 container, including a new EnsureCreated end-to-end case in a fresh database.
  • Every new guard was verified by reverting or neutering the mechanism it guards and watching it fail (verify-the-guard).
  • dotnet pack -c Release: package validation against the 5.0.3 baseline is clean; the API additions are additive.
  • test/consumer-smoke-test.sh passes with the runtime wiring added to both consumer projects, proving the design-time registration still wins when the context's own provider already carries our differ.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 10 commits September 5, 2026 08:12
EF Core's MigrationsModelDiffer.HasDifferences runs its protected Diff, not
the public GetDifferences this package overrides, so every check built on it
reported "no changes" when only a complex index, exclusion constraint or
temporal constraint had changed: dotnet ef migrations
has-pending-model-changes, the pending-model-changes warning Migrate() raises,
and the snapshot check in migrations remove. Route it through GetDifferences.

Bumps the version to 5.1.0 (SECURITY.md table moves with it) and opens the
5.1.0 changelog sections.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s check

EnsureCreated(), GenerateCreateScript() and the pending-model-changes check in
Migrate() run the context's runtime IMigrationsModelDiffer, which the
.targets-injected design-time registration never reaches. With EF's stock
differ there, EnsureCreated() created the tables and silently none of the
complex indexes, and Migrate() never warned about an unscaffolded one.

- core: UseComplexIndexes() / AddComplexIndexes() for providers without a
  satellite
- PostgreSQL: UseNpgsqlComplexIndexes() / AddNpgsqlComplexIndexes() register
  the differ alongside the generator
- SQL Server: UseSqlServerComplexIndexes() / AddSqlServerComplexIndexes()

Tests: SQLite EnsureCreated with and without the registration (the omission is
asserted too), GenerateCreateScript for Npgsql and SQL Server, a live
EnsureCreated against PostgreSQL 18 in a fresh database, and a design-time
registration test with EF's context-seeded factory descriptor in place. The
smoke-test consumers now carry the runtime wiring so the scaffold proves the
design-time registration still wins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A HasComplexIndex selector ending at a ToJson() complex property, or at a
complex collection (always JSON), now resolves to the jsonb container column,
so the idiomatic `USING gin (payload jsonb_path_ops)` is one declaration. The
container is a real column: the stock generator renders it and no runtime
wiring is involved. A complex property nested inside the document resolves to
a `->` extraction (jsonb) and renders as an expression index.

Previously the path failed with "could not resolve property path", and complex
collections could not be indexed at all — their members have no column and EF
10's ComplexCollectionTypePropertyBuilder is not the builder the
property-level API extends. Template placeholders that resolve to a container
column are now quoted as columns rather than parenthesized as expressions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…table

The base differ emits the native index and this differ the complex one,
neither seeing the other, so the migration scaffolded two CREATE INDEX
statements under one name and failed at apply time (42P07). The differ's own
name check only compared complex indexes with each other. Only the target
model's native indexes are consulted, so an index moving between a native and
a complex declaration under one name keeps diffing as a drop and a create.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PostgreSQL storage parameters (WITH (fillfactor=70), WITH (fastupdate=false))
are per-parameter annotations under Npgsql:StorageParameter:, which Npgsql's
own generator renders from the operation — so column indexes need no runtime
wiring. The whitelist and the unknown-Npgsql-key rejection matched exact keys
and both gain a prefix rule; the custom generator renders the clause for
expression indexes in Npgsql's position, after INCLUDE / NULLS NOT DISTINCT and
before WHERE, with Npgsql's value formatting.

Also adds the README bullet for whole-document JSON indexes that the previous
commit left out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Npgsql's generator reads index collations from Relational:Collation on the
operation, but that key on a *property* is the column's collation — so the
option is stored under Npgsql's model key (Npgsql:IndexCollation) and mapped at
stamping time through a new core seam, ToOperationAnnotationName. Comparison
stays on the stored key, so diffing is unaffected. The custom generator renders
COLLATE before the operator class for expression indexes, as Npgsql orders it.
A column's own collation is asserted never to reach the index.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Entity-level provider options reach the operation unfiltered and were always
rejected by the satellite's ValidateCreateIndexOperation. Property-level
options go through the forwarding whitelist, which dropped the other
provider's keys without a word: a property-level .UseGin() diffed by the SQL
Server satellite scaffolded a plain B-tree. Each satellite now forwards the
other provider's prefix solely so the same validation rejects it, keeping one
message for both declaration styles. The PostgreSQL differ also gains the
SqlServer:* rejection it lacked for either path — those options previously
reached Npgsql's generator, which ignored them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
EF hands back the non-generic builder for properties configured by name
(c.Property("Value")) or by type; the property-level API existed only on the
generic one, so those properties could not carry a complex index — the call
did not compile. The typed overloads now delegate to the non-generic ones and
keep their typed return.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every descriptor scan skipped entity types with no table, so an index or
constraint declared on the abstract base of a TPC hierarchy produced nothing —
no DDL, no error. Under EF Core 10 the base's complex columns are not mapped
onto the concrete tables either, so the declaration cannot be satisfied
anywhere; migrations add now says so. The satellites use the same helper for
exclusion and temporal descriptors. View-, query- and function-mapped types
keep being skipped, since an index on those is nothing this package could
create.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@CaffeinatedCoder
CaffeinatedCoder merged commit 2110a80 into main Sep 5, 2026
9 checks passed
@CaffeinatedCoder
CaffeinatedCoder deleted the release/5.1.0 branch September 5, 2026 06:42
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