Allow detection of state for entities with generated keys to be switched off
I have a two models (parent, child) with GUID as PrimaryKey. In a one-to-many relation. By default, the option "ValueGeneratedOnAdd" is activated for PrimaryKey as described here: https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#primary-keys
Now I would like to add child-models which have already a primary key set by the client application. I add the child-models to an list of childs in the parent-model which already exists in database and is loaded before.
In that case the context tracks this child-entites as modified also described here (information card): https://learn.microsoft.com/en-us/ef/core/modeling/keys?tabs=data-annotations#key-types-and-values
Doesn't Work:
public async Task<IActionResult> GetDemo()
{
var parent = await context.Parents.FindAsync(Guid.Parse("0b2e2dc0-fd67-11ee-8fec-84a93832722b"));
// child has a primary key set
var child = new Child("child1", Guid.NewGuid());
parent.Children.Add(child);
// EntityEntry (child) state is: modified
await context.SaveChangesAsync();
return Ok();
}
Works:
public async Task<IActionResult> GetDemo()
{
var parent = await context.Parents.FindAsync(Guid.Parse("0b2e2dc0-fd67-11ee-8fec-84a93832722b"));
// child has none primary key set
var child = new Child("child1");
parent.Children.Add(child);
// EntityEntry (child) state is: added
await context.SaveChangesAsync();
return Ok();
}
On SaveChanges I get the following 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.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, 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.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.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
If I follow the approche, described here: https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#overriding-value-generation
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().Property(x => x.PrimaryKey)
.ValueGeneratedOnAddOrUpdate()
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
}
This cannot be configured for properties which are configured as primary key.
How can I configure to use provided primary or generate if null?
EF Core version: 8.0.2 Database provider: Pomelo.EntityFrameworkCore.MySql Target framework: NET 8.0
When a property is marked as value-generated, as your key property is by convention, then EF will generate a key value unless a non-default key value has already been set. However, if you do set a key value explicitly, then you will need to also tell EF explicitly that the entity is new--otherwise EF treats it as existing because it has a key value. This can be done by tracking the entity by calling Add on the context or DbSet directly. For example:
var child = new Child("child1", Guid.NewGuid());
context.Add(child);
parent.Children.Add(child);
We use our DbContext behind a repository pattern where the parent-entity is the aggregateroot and therefore we don't have public access to DbSet<Child>.
We would like to achieve an approach where we can use client-side and server-side generated keys. In this case it would be helpful if we could disable this behavior regarding "with key" is modified and "without key" an entity is added.
@flostony There currently isn't any way to disable this behavior. We will consider it.