-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Bug description
The issue can best be demonstrated using a simple sample application. Explaining it purely in text is relatively difficult.
The structure of the sample application is as follows:
- There is a base class EntityBase which contains a RowVersion property used as a concurrency token.
- EntityA and EntityB inherit from EntityBase and add their own additional properties.
- EntityB additionally contains a complex type OwnedEntity, which is mapped as an owned type.
- The DbContext configuration is relatively simple.
When the following steps are performed:
- Load an existing EntityA
- Delete that entity
- Create and save a new EntityB using the same primary key
the following exception is thrown:
'Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)'
The issue only occurs with this specific combination of:
- inheritance
- RowVersion configured as a concurrency token, and
- the presence of an owned entity.
The problem can be worked around by either:
- removing the OwnedEntity from EntityB (including its mapping), or
- commenting out the b.Property(x => x.RowVersion) configuration in the DbContext.
Your code
public abstract class EntityBase(string id)
{
public string Id { get; } = id;
public long RowVersion { get; }
}
public record OwnedEntity(DateTime CreationDate);
public class EntityA(string id, bool someValue, OwnedEntity? owned) : EntityBase(id)
{
protected EntityA()
: this(null!, false, null)
{
}
public bool SomeValue { get; } = someValue;
public OwnedEntity? Owned { get; private set; } = owned;
}
public class EntityB(
string id,
string name) : EntityBase(id)
{
protected EntityB() : this(null!, null!)
{
}
public string Name { get; } = name;
}
public class MyDbContext(DbContextOptions<MyDbContext> options) : DbContext(options)
{
protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityBase>(
b =>
{
b.HasDiscriminator<string>("Type")
.HasValue<EntityA>(nameof(EntityA))
.HasValue<EntityB>(nameof(EntityB));
b.HasKey(x => x.Id);
b.Property(x => x.Id).HasMaxLength(10);
b.Property(x => x.RowVersion)
.IsRequired()
.IsRowVersion()
.IsConcurrencyToken()
.HasConversion<byte[]>();
});
modelBuilder.Entity<EntityA>(
b =>
{
b.Property(x => x.SomeValue);
b.OwnsOne(
x => x.Owned,
x =>
{
x.Property(x => x.CreationDate);
});
});
modelBuilder.Entity<EntityB>(
b =>
{
b.Property(x => x.Name).HasMaxLength(100);
});
}
}
const string id = "SOMEID";
await using var dbContext = MyContextFactory.Create();
var entityA = await dbContext.Set<EntityA>().Where(x => x.Id == id).SingleAsync();
dbContext.Remove(entityA);
var entityB = new EntityB(id, "Any");
await dbContext.AddAsync(entityB);
await dbContext.SaveChangesAsync();Stack traces
Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetAsync(Int32 startCommandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Update.Internal.SqlServerModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChangesAsync(IList`1 entries, 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 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Program.<Main>$(String[] args) in C:\Users\rs\Desktop\ConsoleApp\ConsoleApp\Program.cs:line 27
at Program.<Main>$(String[] args) in C:\Users\rs\Desktop\ConsoleApp\ConsoleApp\Program.cs:line 27
at Program.<Main>(String[] args)
Process finished with exit code -532,462,766.
Verbose output
EF Core version
10.0.2
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.Net 10
Operating system
Windows 11
IDE
No response