Skip to content

Upgrade guide: 'nothing of it is applied' is false for an index declared with IsCreatedConcurrently() #171

Closed

Description

@CaffeinatedCoder

Found by a pre-release sweep of main @ eff80b4 (lens: docs-claims); reproduced in a scratch test. Before the tag, but not blocking it: the 5.5.0 release notes link this page at blob/v5.5.0, so the sentence is frozen there.

Problem

docs/upgrading.md, in the entry "Declarations on a TPC type cover every table of the hierarchy", says: "Before you apply it, remove the rows in the derived tables that violate a unique index or an exclusion constraint. The migration fails on them … The migration runs in a transaction, so nothing of it is applied." The 934ec55 wording ("in its transaction applies nothing") was the same.

That isn't true for an index declared with IsCreatedConcurrently(), which the 5.4.0 entry on the same page recommends for large tables. The TPC copies inherit the flag, and Npgsql renders each copy as a command with its transaction suppressed. EF's migrator commits the open transaction before it runs such a command. When a concurrent unique copy then fails on duplicates (23505):

  • every operation before it is already committed;
  • PostgreSQL leaves an INVALID index under the copy's name;
  • the migration isn't recorded, so re-running it fails on what was already applied (42P07 or similar).

Scenario

Booking is TPC on bookings, with SpecialBooking on special_bookings, and declares b.HasComplexIndex(x => x.Code, i => i.IsUnique().IsCreatedConcurrently()). Its generated commands, from the differ through the wired Npgsql generator:

suppressed=False: CREATE TABLE bookings (…
suppressed=False: CREATE TABLE special_bookings (…
suppressed=True: CREATE UNIQUE INDEX CONCURRENTLY "IX_bookings_Code" ON bookings ("Code");
suppressed=True: CREATE UNIQUE INDEX CONCURRENTLY "IX_special_bookings_Code" ON special_bookings ("Code");

That TransactionSuppressed commands run outside the migration's transaction, and that a failed CREATE INDEX CONCURRENTLY leaves an invalid index, is documented EF Core and PostgreSQL behaviour. The concurrent path wasn't applied live in this sweep.

Change

Qualify the sentence: an index declared with IsCreatedConcurrently() is created outside the transaction. If it fails, earlier operations stay applied, and the invalid index has to be dropped (DROP INDEX CONCURRENTLY) before the migration runs again. Better still, tell the reader to remove the violating rows first in that case too.

Test

Docs only; no test.

Activity

  1. added this to the 5.5.0 milestone on Oct 20, 2026
  2. added
    documentationImprovements or additions to documentation
    package/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL
    area/migrationsThe differs, snapshot format, EnsureCreated and design-time wiring
    on Oct 20, 2026
  3. added a commit that references this issue on Oct 20, 2026
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/migrationsThe differs, snapshot format, EnsureCreated and design-time wiringdocumentationImprovements or additions to documentationpackage/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions