Skip to content

Moving a table into the default schema drops and re-creates every complex index and constraint on it (PostgreSQL: fails with 42P01) #185

Closed

Description

@CaffeinatedCoder

Found by a sweep of release/5.x @ d28c938 for base operations the differs do not account for (the shape of #182); reproduced in scratch MigrationHarness.SqlServerDiff / NpgsqlDiff tests and applied to PostgreSQL 18 (Testcontainers).

Problem

Moving a table into the default schema drops and re-creates every complex index, exclusion constraint, temporal constraint and temporal foreign key on it. A move between two named schemas, or from the default schema into a named one, is handled correctly.

EF Core's RenameTableOperation always carries the target schema in NewSchema, and null there means the default schema. EF Core's SQL Server generator reads it that way (ALTER SCHEMA <SCHEMA_NAME()> TRANSFER). The differs read null as "unchanged", so a source on north.bookings is compared under north.bookings against a target on bookings, and nothing matches:

SqliteRebuiltTables (CustomMigrationsModelDiffer.cs:469) already reads rename.NewSchema as-is.

Model: bookings with ToTable("bookings", "north") → ToTable("bookings"), with no HasDefaultSchema. It has a unique property-level HasComplexIndex on Contact.Email and a composite ix_bookings_code_email.

SQL Server, rendered with the target model:

DROP INDEX [IX_bookings_Contact_Email] ON [north].[bookings];
DROP INDEX [ix_bookings_code_email] ON [north].[bookings];
DECLARE @defaultSchema nvarchar(max) = QUOTENAME(SCHEMA_NAME());
EXEC(N'ALTER SCHEMA ' + @defaultSchema + N' TRANSFER [north].[bookings];');
CREATE UNIQUE INDEX [IX_bookings_Contact_Email] ON [bookings] ([Contact_Email]) WHERE [Contact_Email] IS NOT NULL;
CREATE INDEX [ix_bookings_code_email] ON [bookings] ([Code], [Contact_Email]);

ALTER SCHEMA … TRANSFER keeps the table's indexes, so both rebuilds are needless: an offline index build on a schema move.

PostgreSQL fails outright. Npgsql 10 renders nothing for a RenameTableOperation whose NewSchema is null (NpgsqlMigrationsSqlGenerator.cs L890 at v10.0.0), so the table stays in north. Without this package that is Npgsql's own silent no-op. With it, the drops run and the creates name a table that is not there:

ALTER TABLE "north"."scratch_moved" DROP CONSTRAINT IF EXISTS "ex_moved_room";
DROP INDEX north.ix_moved_code_email;
CREATE INDEX ix_moved_code_email ON scratch_moved ("Code", "Contact_Email");
-- 42P01: relation "scratch_moved" does not exist

With a temporal constraint on the table it also issues DropUniqueConstraint and ADD CONSTRAINT … WITHOUT OVERLAPS, for the same reason.

Change

Read NewSchema as EF Core writes it: the target schema, null for the default. In all three places, use (rename.NewName ?? rename.Name, rename.NewSchema). A pair that matches after normalization produces no operation, and on PostgreSQL the migration then does what Npgsql does without the package.

This changes what an existing model scaffolds, so it ships in a minor with a ### Changed answers row and a docs/upgrading.md entry.

Tests

  • SqlServerDiff and NpgsqlDiff tests for the model above: only EnsureSchema/RenameTable, with no index or constraint operations. Include an exclusion constraint, a temporal constraint and a temporal foreign key whose principal moves.
  • A column renamed on the same table in the same migration: still only the RenameColumnOperation.
  • The moves that already work (north → south, default → north), so the fix does not turn them around.

Activity

  1. added this to the 5.6.0 milestone on Oct 23, 2026
  2. added
    bugSomething isn't working
    severity/mediumInconsistency or failure in a narrow scenario
    package/coreAffects EFCore.ComplexIndexes
    package/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL
    package/sqlserverAffects EFCore.ComplexIndexes.SqlServer
    area/indexesComplex-property, composite, unique and filtered indexes
    area/constraintsTemporal (WITHOUT OVERLAPS, PERIOD) and exclusion constraints
    area/migrationsThe differs, snapshot format, EnsureCreated and design-time wiring
    on Oct 23, 2026
  3. added a commit that references this issue on Oct 23, 2026
  4. CaffeinatedCoder commented on Oct 23, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Fixed on release/5.x by #191, shipping in 5.6.0. Closed by hand: Closes acts only on merges into main.

    RenameTableOperation.NewSchema is now read as EF Core writes it (the target's schema, null for the default one), so a move into the default schema keeps the indexes and constraints on the table. On PostgreSQL the migration now runs; Npgsql 10 still writes no statement for that move, so the table stays in its schema, as it does without this package. docs/upgrading.md says to name the schema to move it.

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 indexesarea/migrationsThe differs, snapshot format, EnsureCreated and design-time wiringbugSomething isn't workingpackage/coreAffects EFCore.ComplexIndexespackage/postgresqlAffects EFCore.ComplexIndexes.PostgreSQLpackage/sqlserverAffects EFCore.ComplexIndexes.SqlServerseverity/mediumInconsistency or failure in a narrow scenario

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions