Swapping property values between elements with a unique constraint results in unique constraint violation
conreaux opened this issue · 0 comments
When entities in a collection contain a property with a unique constraint, and a "swap" is attempted between those property values, the result is a unique constraint error.
I created a test case to demonstrate. It works without issue when no unique constraint is present.
But when the unique constraint is placed on Title
, the error occurs. I assume this is because the
first row to be updated violates the unique constraint due to the second row's values.
Is there a recommended solution for this situation?
Also, it was necessary to modify the AutoMapper.Collection.EntityFrameworkCore.Tests
to use SQLite in-memory database
to add the following constraint to DbContextBase
:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Thing>()
.HasIndex(e => e.Title)
.IsUnique();
}
The test case and exception message are shown below:
[Fact]
public async Task Persist_InsertOrUpdateAsync_WhenTitlesSwapped_ThenEntitiesShouldHaveExpectedTitles()
{
// Arrange [mapper and db fields set in ctor]
db.Things.Add(new Thing { Title = "Test1" });
db.Things.Add(new Thing { Title = "Test2" });
await db.SaveChangesAsync();
var item1 = await db.Things.Where(x => x.Title == "Test1").FirstAsync();
var item2 = await db.Things.Where(x => x.Title == "Test2").FirstAsync();
// Act
var p = db.Things.Persist(mapper);
var update1 = await p.InsertOrUpdateAsync(new ThingDto { ID = item1.ID, Title = "Test2" }); // Test1 => Test2
var update2 = await p.InsertOrUpdateAsync(new ThingDto { ID = item2.ID, Title = "Test1" }); // Test2 => Test1
await db.SaveChangesAsync();
// Assert
(await db.Things.CountAsync()).Should().Be(2);
(await db.Things.FirstOrDefaultAsync(x => x.ID == item1.ID))?.Title.Should().Be("Test2");
(await db.Things.FirstOrDefaultAsync(x => x.ID == item2.ID))?.Title.Should().Be("Test1");
}
AutoMapper.Collection.EntityFrameworkCore.Tests.EntityFrameworkCoreUsingCtorTests.Persist_InsertOrUpdateAsync_NonUniqueThings
Source: EntityFrameworkCoreTestsBase.cs line 448
Duration: 968 ms
Message:
Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while saving the entity changes. See the inner exception for details.
---- Microsoft.Data.Sqlite.SqliteException : SQLite Error 19: 'UNIQUE constraint failed: Things.Title'.
Stack Trace:
ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
EntityFramworkCoreTestsBase.Persist_InsertOrUpdateAsync_NonUniqueThings() line 453
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
SqliteDataReader.NextResult()
SqliteCommand.ExecuteReader(CommandBehavior behavior)
SqliteCommand.ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
SqliteCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)