Skip to content

SchemaKit: leave out what EF Core 11 and Npgsql 11 do natively; keep what stays unique #39

Open

Description

@CaffeinatedCoder

Decided 2026-10-19 (temporal half): leave out. SchemaKit ships no HasTemporalConstraint and no temporal foreign key. Npgsql 11's HasKey(…).WithoutOverlaps() and WithPeriod() cover NpgsqlRange<T> and, since CaffeinatedCoder/CodoMetis.ValueRanges#335, every ValueRanges range and multirange column (measured, comment below). The one gap is Npgsql's own NpgsqlRange<T>[] multirange, which Npgsql rejects; that is Npgsql's to close, and the migration guide names it. Still open here: the HasComplexIndex half (plain, composite, unique, filtered).

Rewritten 2026-10-19 for the spin-off (#37). SchemaKit is a new package, so nothing is delegated or kept as [Obsolete]: each feature is either left out or kept.

On EF Core 11 the platform does two things this package was built for:

For each of HasComplexIndex (plain, composite, unique, filtered) and HasTemporalConstraint / temporal foreign keys, decide and record here, with a measured SQL diff against EFCore.ComplexIndexes 5.x:

The temporal half waits for CaffeinatedCoder/CodoMetis.ValueRanges#150. It measures Npgsql 11's WithoutOverlaps/WithPeriod over ValueRanges' DateRange, RangeSet (multirange) and custom timerange columns on PostgreSQL 18. Npgsql documents the native API with NpgsqlRange<T>. If it refuses a period that isn't one, HasTemporalConstraint stays in SchemaKit for every other range CLR type.

Everything left out goes into the 11.0.0 changelog's list of what can break you. Nothing here touches the 5.x line.

Activity

  1. added this to the 11.0.0 milestone on Oct 18, 2026
  2. added
    enhancementNew feature or request
    breakingChanges an answer or a signature — needs a major
    package/coreAffects EFCore.ComplexIndexes
    package/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL
    area/indexesComplex-property, composite, unique and filtered indexes
    area/constraintsTemporal (WITHOUT OVERLAPS, PERIOD) and exclusion constraints
    on Oct 18, 2026
  3. changed the title [-]Delegate to native EF Core 11 complex-type indexes and Npgsql 11 temporal constraints; keep what stays unique[/-] [+]SchemaKit: leave out what EF Core 11 and Npgsql 11 do natively; keep what stays unique[/+] on Oct 19, 2026
  4. CaffeinatedCoder commented on Oct 19, 2026

    @CaffeinatedCoder
    OwnerAuthor

    CaffeinatedCoder/CodoMetis.ValueRanges#150, measured 2026-10-19 on PostgreSQL 18 (postgres:18-alpine)

    Short answer: Npgsql 11 refuses ValueRanges columns as they are today, on rc.1.1 and on rc.2. On rc.2 a one-line change in ValueRanges makes the native API accept all three types, the multirange included.

    What Npgsql checks

    The native API is HasKey(…).WithoutOverlaps() and HasOne(…).WithMany(…).HasForeignKey(…).WithPeriod(). The issue's HasWithoutOverlaps/HasPeriod don't exist.

    NpgsqlModelValidator checks the last key property's type mapping, not the store type name:

    • rc.1.1: typeMapping is not NpgsqlRangeTypeMapping, which accepts Npgsql's own range mappings only.
    • rc.2: typeMapping is not INpgsqlRangeTypeMapping, an interface introduced for NodaTime's ranges (Support NodaTime ranges in temporal constraints npgsql/efcore.pg#3975). It has one member, IComparer? CreateCurrentValueComparer(Type), which supplies a key comparer for types that aren't IComparable.

    The check also requires SetPostgresVersion(18, 0). Without it the model throws WITHOUT OVERLAPS … requires PostgreSQL 18.0 or later.

    Results

    Period column rc.1.1 / rc.2 as is rc.2 + INpgsqlRangeTypeMapping on the ValueRanges mappings
    NpgsqlRange<DateOnly> (Npgsql's own, baseline) PRIMARY KEY ("RoomId", "Period" WITHOUT OVERLAPS); overlap → 23P01 same
    DateRange (daterange) InvalidOperationException: WITHOUT OVERLAPS … requires the last column to be a PostgreSQL range type …, but property 'Period' has type 'DateRange' renders; adjacent accepted; overlap → DbUpdateException / 23P01 on PK_Slots; rows read back
    TimeRange (custom timerange) same refusal renders (with btree_gist); same results
    RangeSet<DateRange, DateOnly> (datemultirange) same refusal renders; same results
    NpgsqlRange<DateOnly>[] (Npgsql's own multirange) refused refused (Npgsql's multirange mapping doesn't implement the interface)
    PERIOD FK over DateRange PERIOD on foreign key … requires the last column to be a PostgreSQL range type FOREIGN KEY ("RoomId", PERIOD "Period") REFERENCES "Principals" ("RoomId", PERIOD "Period"); a period covered by two adjacent principals is accepted; an uncovered one → 23503

    PostgreSQL 18 itself accepts all of it. Hand-written PRIMARY KEY (room, days WITHOUT OVERLAPS) over datemultirange and over timerange rejects overlaps with 23P01.

    A table created that way, with rows written through EF using an ordinary key over DateRange, rejects the overlap too (23P01). That is the workaround on rc.1.1.

    The change that makes it work

    ValueRangeTypeMapping and ValueRangeSetTypeMapping implement INpgsqlRangeTypeMapping explicitly and return null from CreateCurrentValueComparer. Every range type and RangeSet is already IComparable<T> in PostgreSQL's order, so EF's default comparer applies and Npgsql's convention adds nothing.

    The interface is only read in three places on rc.2: the validator and the two conventions that set that comparer. So the change alters no query translation. It needs the Npgsql floor at rc.2 (CaffeinatedCoder/CodoMetis.ValueRanges#332).

    Consequence for this issue

    With this change in ValueRanges 11, Npgsql's native temporal keys cover ValueRanges' DateRange, TimeRange and RangeSet (multirange) columns, and their PERIOD foreign keys. SchemaKit doesn't need to keep HasTemporalConstraint for ValueRanges' sake.

    It still needs it for an NpgsqlRange<T>[] multirange period, which Npgsql refuses. Whether that is reason enough is #39's call.

  5. CaffeinatedCoder commented on Oct 19, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Maintainer decision 2026-10-19: leave temporal constraints out of SchemaKit (recorded at the top of the issue). The HasComplexIndex half stays open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/constraintsTemporal (WITHOUT OVERLAPS, PERIOD) and exclusion constraintsarea/indexesComplex-property, composite, unique and filtered indexesbreakingChanges an answer or a signature — needs a majorenhancementNew feature or requestpackage/coreAffects EFCore.ComplexIndexespackage/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions