Skip to content

SaveChanges circular dependency in unique filtered index  #28065

@jgilchrist

Description

@jgilchrist

While testing out the newest preview, I'm running into an issue with a previously working bit of code - BatchingTopologicalSort is throwing errors for a case that was working in EFCore 6.

It's reproducible for me with the following cut-down code:

Entities:

public class TestEntity
{
    public int Id { get; set; }
}

public class TestDependentEntity
{
    public int Id { get; set; }
    public int TestEntityId { get; set; }

    public bool? UniqueOn { get; set; }
    public int? ToChange { get; set; }

    public class Config : IEntityTypeConfiguration<TestDependentEntity>
    {
        public void Configure(EntityTypeBuilder<TestDependentEntity> builder)
        {
            builder.HasIndex(e => new { e.TestEntityId, e.UniqueOn })
                .IsUnique();
        }
    }
}

To reproduce:

var dbContext = new TestDbContext();

var t1 = new TestEntity();

db.Tests.Add(t1);

await db.SaveChangesAsync();

var dependent = new TestDependentEntity() {
    TestEntityId = t1.Id,
    UniqueOn = null,
    ToChange = 1,
};

var dependent2 = new TestDependentEntity() {
    TestEntityId = t1.Id,
    UniqueOn = null,
    ToChange = 2,
};

db.TestDependents.Add(dependent);
db.TestDependents.Add(dependent2);

await db.SaveChangesAsync();

dependent.ToChange = null;
dependent2.ToChange = null;

await db.SaveChangesAsync();

The final SaveChangesAsync() will throw the following error:

System.InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved: 'TestDependentEntity { 'Id': 2 } [Modified] <-
Index { 'test_entity_id': 1, 'unique_on':  } TestDependentEntity { 'Id': 3 } [Modified] <-
Index { 'test_entity_id': 1, 'unique_on':  } TestDependentEntity { 'Id': 2 } [Modified]'.
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.ThrowCycle(List`1 cycle, Func`2 formatCycle, Func`2 formatException)
   at Microsoft.EntityFrameworkCore.Utilities.Multigraph`2.BatchingTopologicalSort(Func`4 tryBreakEdge, Func`2 formatCycle)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.TopologicalSort(IEnumerable`1 commands)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IList`1 entries, IUpdateAdapter updateAdapter)+MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

It appears that this is due to the unique index, and indeed removing the index makes this work. Although I can't tell for sure, my hunch is that the code working with the index is not considering the fact that the two rows are considered unique by the index even though they both have null in UniqueOn because NULL != NULL?

Happy to provide any more info if needed.

Info

EF Core version: 7.0.0-preview.4.22229.2
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL
Target framework: .NET 6
Operating system: macOS
IDE: Rider 2022.1

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions