Skip to content

Resolve a temporal foreign key's generic principal from a migration snapshot (#197) - #199

Merged
CaffeinatedCoder merged 2 commits into
release/5.xfrom
fix/197-generic-temporal-principal
Oct 23, 2026
Merged

CaffeinatedCoder merged 2 commits into
release/5.xfrom
fix/197-generic-temporal-principal

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #197

Into release/5.x. Found by the focused 5.6.0 sweep (round-trip lens). It has been present since temporal foreign keys shipped; it isn't a 5.6.0 regression. Closes acts only on main, so #197 is closed by hand after the merge.

Problem

A temporal foreign key whose principal is a generic entity type (Room<int>) couldn't be diffed from its migration snapshot. The declaration stores the principal as typeof(TPrincipal).FullName, which is …Room`1[[System.Int32, …]], while EF names the entity type …Room<int>. The live model matched through ClrType.FullName. A snapshot's property-bag type has ClrType == Dictionary<string, object>, so ResolveEntityType found nothing and threw. The first migrations add passed. Every later one, has-pending-model-changes, and Migrate()'s pending-changes check failed, even with no change. It also hid the snapshot path of #187/#188's tests, which use generic types.

Change

  • EntityTypeNames.FromClrFullName (new, internal, PostgreSQL package) converts a CLR FullName to EF Core's entity-type name, following SharedTypeExtensions.DisplayName(fullName: true) in EF Core 10: keywords for built-in types, T? for Nullable<T>, each segment of a nested generic type with its own arguments, and array ranks as written. Input it can't read comes back unchanged.
  • ResolveEntityType matches the stored name, its EF form, or the CLR FullName.
  • Stored names are unchanged. Snapshots already hold the FullName form, and writing another would rewrite them.

Also: a ### Fixed line in the root and PostgreSQL changelogs, and CLAUDE.md.

Tests

  • SnapshotRoundTripTests.Temporal_foreign_key_to_a_generic_principal_roundtrip_is_noop: a temporal FK to RoundTripGenericRoom<int>, diffed from its Roslyn-compiled snapshot against the unchanged model, yields no operations.
  • EntityTypeNameTests: EF's own model is the oracle. For 11 shapes the conversion of type.FullName equals the name ModelBuilder.Entity(type) gives: non-generic, Generic<int>, <string>, <Guid>, <int?>, <int[]>, <List<long>>, <Generic<bool>>, Pair<int, string>, Pair<DateOnly?, Plain>, and Outer<int>.Inner.
  • With the ResolveEntityType change reverted: the snapshot test fails with "Could not resolve temporal foreign key principal entity …Room`1[[System.Int32…".
  • Suites: unit plus PostgreSQL 18/16, 746 passed.

🤖 Generated with Claude Code

…ion snapshot

A temporal foreign key stores its principal as the CLR FullName, which
for a generic type is "Room`1[[System.Int32, ...]]" where EF names the
entity type "Room<int>". The live model matched through ClrType; a
snapshot's property-bag type has no CLR type, so the principal did not
resolve and every diff from the snapshot threw: each migrations add
after the first, has-pending-model-changes, and Migrate()'s
pending-changes check.

EntityTypeNames.FromClrFullName converts the stored name by EF Core's
own display rule (keywords for built-in types, T? for Nullable<T>,
nested generic segments, array ranks), and ResolveEntityType matches
either form. Stored names are unchanged, since snapshots hold them.

Closes #197

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CaffeinatedCoder CaffeinatedCoder added this to the 5.6.0 milestone Oct 23, 2026
@CaffeinatedCoder CaffeinatedCoder added bug Something isn't working severity/medium Inconsistency or failure in a narrow scenario package/postgresql Affects EFCore.ComplexIndexes.PostgreSQL area/constraints Temporal (WITHOUT OVERLAPS, PERIOD) and exclusion constraints area/migrations The differs, snapshot format, EnsureCreated and design-time wiring labels Oct 23, 2026
…c-temporal-principal

# Conflicts:
#	CHANGELOG.md
#	src/EFCore.ComplexIndexes.PostgreSQL/CHANGELOG.md
@CaffeinatedCoder
CaffeinatedCoder merged commit 8975493 into release/5.x Oct 23, 2026
13 checks passed
@CaffeinatedCoder
CaffeinatedCoder deleted the fix/197-generic-temporal-principal branch October 23, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/constraints Temporal (WITHOUT OVERLAPS, PERIOD) and exclusion constraints area/migrations The differs, snapshot format, EnsureCreated and design-time wiring bug Something isn't working package/postgresql Affects EFCore.ComplexIndexes.PostgreSQL severity/medium Inconsistency or failure in a narrow scenario

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant