Description
openedon Feb 16, 2022
On attempting to upgrade our project from EF Core 5 to 6, most of our integration tests fail. I've narrowed this down to a small repro below. An entity Foo
has many FooPeriods
. One of these is 'current'. So we have a 1:many and a 1:1 relationship:
public class Foo
{
public int Id { get; set; }
public int? CurrentPeriodNumber { get; set; }
public FooPeriod? CurrentPeriod { get; set; }
public ICollection<FooPeriod> Periods { get; } = new HashSet<FooPeriod>();
}
public class FooPeriod
{
public int FooId { get; set; }
public int PeriodNumber { get; set; }
public Foo? Foo { get; set; }
}
This is configured via the fluent builder as below:
public class Context : DbContext
{
public Context(DbContextOptions<Context> options)
:base(options)
{
}
public DbSet<Foo> Foos => Set<Foo>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>(entity =>
{
entity.HasKey(x => x.Id);
entity.Property(x => x.Id)
.ValueGeneratedOnAdd();
entity.HasOne(x => x.CurrentPeriod)
.WithOne()
.HasForeignKey<Foo>(x => new { x.Id, x.CurrentPeriodNumber });
});
modelBuilder.Entity<FooPeriod>(entity =>
{
entity.HasKey(x => new { x.FooId, x.PeriodNumber });
entity.HasOne(x => x.Foo)
.WithMany(x => x.Periods)
.HasForeignKey(x => x.FooId);
});
}
}
If I insert a new period as below:
var period = new FooPeriod
{
PeriodNumber = 1,
Foo = new Foo()
};
context.Add(period);
context.SaveChanges();
Then I get this exception:
Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> System.InvalidOperationException: The property 'FooPeriod.FooId' cannot be assigned a value generated by the database. Store-generated values can only be assigned to properties configured to use store-generated values.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetStoreGeneratedValue(IProperty property, Object value)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PropagateValue(InternalEntityEntry principalEntry, IProperty principalProperty, IProperty dependentProperty, Boolean isMaterialization, Boolean setModified)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.SetForeignKeyProperties(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, Boolean setModified, Boolean fromQuery)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IEnumerable`1 containingPrincipalKeys, IEnumerable`1 containingForeignKeys, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IEnumerable`1 keys, IEnumerable`1 foreignKeys, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectKeyChange(InternalEntityEntry entry, IProperty property)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.PropertyChanged(InternalEntityEntry entry, IPropertyBase propertyBase, Boolean setModified)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.PropertyChanged(InternalEntityEntry entry, IPropertyBase property, Boolean setModified)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete, CurrentValueType valueType)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetStoreGeneratedValue(IProperty property, Object value)
at Microsoft.EntityFrameworkCore.Update.ColumnModification.set_Value(Object value)
at Microsoft.EntityFrameworkCore.Update.ModificationCommand.PropagateResults(ValueBuffer valueBuffer)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithPropagation(Int32 commandIndex, RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__104_0(DbContext _, ValueTuple`2 t)
at Microsoft.EntityFrameworkCore.Storage.NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
I've dug around to see what might have changed between 5 and 6 and am pretty confident the change in behaviour was caused by the fix for #22603. I also note that this is the same sort of model as #26629 (which prevented us updating to 6.0.0).
EF Core version: 6.0.2
Database provider: I've tried Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Sqlite
Target framework: .NET 6.0
Operating system: macOS