From 4f72fe145a91d7e3feb86e9aa5efd5878f8b76b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Gro=C3=9F?= Date: Sat, 5 Sep 2026 12:09:35 +0200 Subject: [PATCH 1/3] fix: resolve converter-member paths against a model snapshot; open 5.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A snapshot persists a converted property as its provider type on a property-bag type and drops the converter, so the member check that guards `x.CreatedAt.Year` had nothing to check against. The first `migrations add` passed because the snapshot did not hold the path yet; every diff whose source was the resulting snapshot then threw `Could not resolve property path 'Email.Value'` — the next `migrations add`, `has-pending-model-changes`, and the pending-changes check `Migrate()` runs before applying anything. On a property-bag type, an indexer property with no converter now resolves as the persisted scalar. Entity and complex types in a snapshot are both property bags, so one fallback covers expression templates, column parts, composite parts, filter placeholders and exclusion elements, at the top level and nested. SnapshotRoundTripTests declares every one of those forms and diffs the compiled snapshot against the live model with both the harness differ and the runtime differ AddNpgsqlComplexIndexes() registers. Also: AGENTS.md pointing agents at CLAUDE.md, and consumer names removed from the changelog and test comments. Co-Authored-By: Claude Fable 5.1 --- AGENTS.md | 5 ++ CHANGELOG.md | 9 +- CLAUDE.md | 10 ++- Directory.Build.props | 4 +- SECURITY.md | 4 +- src/EFCore.ComplexIndexes/CHANGELOG.md | 10 +++ .../CustomMigrationsModelDiffer.cs | 24 +++-- .../NpgsqlExclusionConstraintDifferTests.cs | 2 +- .../NpgsqlJsonMemberIndexTests.cs | 2 +- .../PostgresIntegrationTests.cs | 2 +- .../SnapshotRoundTripTests.cs | 89 +++++++++++++++++++ 11 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f46308f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md + +`CLAUDE.md` is the canonical source of repository instructions. + +Before working in this repository, every coding agent that does not load `CLAUDE.md` automatically must read it in full and follow all of its guidance. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5235dd2..e4eefd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,16 @@ covering only what changed for that package: [PostgreSQL](src/EFCore.ComplexIndexes.PostgreSQL/CHANGELOG.md), [SQL Server](src/EFCore.ComplexIndexes.SqlServer/CHANGELOG.md). +## 5.3.0 + +One fix, found the first time a 5.2.0 converter-member path met the model snapshot +it had just been scaffolded into. + +- **Fixed:** a property path through a value converter (`x => x.Email.Value`) now resolves against a model snapshot as well as against the configured model. A snapshot persists a converted property as its provider type — `string`, on a property-bag type — and drops the converter, so the member check that guards `x.CreatedAt.Year` had nothing to check against and the path failed to resolve. The first `dotnet ef migrations add` succeeded, because the snapshot did not hold the path yet; everything that diffed the resulting snapshot then threw `Could not resolve property path 'Email.Value'` — the next `migrations add`, `has-pending-model-changes`, and `Migrate()`, whose pending-model-changes check runs the differ this package registers before applying anything. On a property-bag type the persisted scalar is now accepted for a path the configured model already validated; against a configured model the provider-type check is unchanged. Covers expression templates, column parts, composite parts, filter placeholders and PostgreSQL exclusion elements, at the top level and inside a complex type. + ## 5.2.0 -The features AuditOffice's review of its own workarounds asked for, in the order they pay off: +The features a consumer's review of its own workarounds asked for, in the order they pay off: a validation for a failure that reports nothing, a read model so an application can check its own obligations, filters that resolve property paths the way index parts already do, an amend API, and typed filter predicates. diff --git a/CLAUDE.md b/CLAUDE.md index 4a4a10c..2724c88 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -375,7 +375,15 @@ carries on both sides of the diff, so placeholder filters never churn and need n `ResolveProperty` (core, shared by every path walk) also unwraps one member of a converter-mapped value object — `Email.Value` resolves to the `Email` column only when the property has a converter and the member's type equals the converter's provider type; without that check `CreatedAt.Year` -would silently index the whole column. +would silently index the whole column. A model snapshot has neither the value object nor the +converter: it persists the property as its provider type (`string`) on a property-bag type, so the +member check has nothing to check against. On a property-bag type, for an indexer property without +a converter, the resolver therefore accepts the persisted scalar — the path was validated against +the configured model when the snapshot was scaffolded. This matters more than churn: the first +`migrations add` succeeds because the snapshot does not hold the path yet, and everything that +diffs the resulting snapshot fails — the next `migrations add`, `has-pending-model-changes`, and +`Migrate()`, whose pending-changes check throws by default since EF Core 9. `SnapshotRoundTripTests` +covers every place such a path can appear, at the top level and inside a complex type. Typed filters (`NpgsqlTypedFilterExtensions`, PostgreSQL only) run `NpgsqlLinqIndexTranslator.TranslatePredicate` at the declaration and store the resulting diff --git a/Directory.Build.props b/Directory.Build.props index 24a0302..d224267 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 5.2.0 + 5.3.0 CaffeinatedCoder MIT true @@ -63,7 +63,7 @@ --> true - 5.1.0 + 5.2.0