5.1.0: pending-changes visibility, runtime differ, JSON container indexes, Npgsql index options - #28
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
HasDifferencesnever saw this package. EF'sMigrationsModelDiffer.HasDifferencesruns the protectedDiff, not theGetDifferenceswe override, sodotnet ef migrations has-pending-model-changes, the pending-model-changes warning inMigrate(), and the snapshot check inmigrations removeall reported "no changes" for a complex-index-only change. Three-line override in core. A CI gate built onhas-pending-model-changesmay now fail where it passed before — that is the gate working.HasIndexon the same table scaffolded twoCREATE INDEXstatements under one name and failed at apply (42P07). Rejected atmigrations add; the native→complex handover under one name still diffs..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 theSqlServer:*rejection it lacked for either path.New
UseComplexIndexes()(core),UseNpgsqlComplexIndexes()now also registers the differ,UseSqlServerComplexIndexes()(new), plusAdd…ComplexIndexes()for internal service providers.EnsureCreated(),GenerateCreateScript()andMigrate()'s pending-changes check run the runtime differ, which the design-time wiring never reaches. Closes theEnsureCreatedhalf of [Bug] Incorrect migration is generated #4.HasComplexIndex(x => x.Payload, ix => ix.UseGin().HasOperators("jsonb_path_ops"))on aToJson()property or a complex collection indexes thejsonbcontainer column through the stock generator. Nested sub-documents resolve to a->extraction.HasStorageParameter(name, value)andUseCollation(params string[])on PostgreSQL indexes. Collation needed a small core seam (ToOperationAnnotationName): the option is stored under Npgsql's model key becauseRelational:Collationon a property is the column's collation, and mapped to the operation key Npgsql's generator reads.HasComplexIndexon the non-genericComplexTypePropertyBuilder, soc.Property("Value")can carry one.Changed
migrations addinstead 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
EnsureCreatedend-to-end case in a fresh database.dotnet pack -c Release: package validation against the 5.0.3 baseline is clean; the API additions are additive.test/consumer-smoke-test.shpasses 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