Bug description
Consider a scenario with:
- a root entity,
Outer: an outer owned entity owned by the root entity, and
Inner: an inner (nested) owned entity owned by Outer.
Further consider that a row exists in the database with a NULL value in a column that maps to a non-nullable property of Outer. At the same time, columns that map to properties of Inner are present.
This is an inconsistent state in the database, Outer is "partially present" in the underlying data. Still, EF Core will apparently successfully load the root entity, with Outer being null.
When you subsequently try to replace Outer with a (valid) new instance, SaveChanges throws an InvalidOperationException with the message
The instance of entity type 'Inner' cannot be tracked because another instance with the same key value … is already being tracked.
It's very hard to find the root cause of the problem from this exception. But if you don't trigger the exception (e.g., in a read-only code path), it's even more problematic because EF Core will pretend that Outer is null even though there is some data for it in the database. In my opinion, EF Core should throw an exception with a clear error message upon encountering such an inconsistency while hydrating an entity.
This is aggravated by EF Core scaffolding migrations that will produce this state by default (see #25359 ).
Your code
// Add: <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
try
{
await using var context = new TestDbContext();
// Create database, recreating it if it already exists
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// Insert a root entity with inconsistent data
var rootId = Guid.NewGuid();
context.Database.ExecuteSql($@"
INSERT INTO RootEntities (Id, Outer_RequiredProperty, Outer_Inner_InnerProperty)
VALUES ({rootId}, NULL, 42)
");
Console.WriteLine("Database seeded with inconsistent state.");
// Now try to load and modify - this should trigger the bug
Console.WriteLine("Attempting to load and modify the entity...\n");
var root = await context.RootEntities.FindAsync(rootId);
Trace.Assert(root != null);
Console.WriteLine($"Loaded root entity with Id: {root.Id}");
Console.WriteLine($"Outer: {root.Outer?.ToString() ?? "<null>"}");
Console.WriteLine();
// Try to replace the owned entity with a new valid instance
Console.WriteLine("Attempting to replace the outer owned entity with a valid instance...");
root.Outer = new Outer
{
RequiredProperty = 1,
Inner = new Inner
{
InnerProperty = 2
}
};
context.SaveChanges();
Console.WriteLine("✓ Successfully saved the changes.");
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine($"Exception caught: {ex}");
}
// Entity models
public class RootEntity
{
public Guid Id { get; set; }
public Outer? Outer { get; set; }
}
public record Outer
{
public required int RequiredProperty { get; set; }
public required Inner? Inner { get; set; }
}
public record Inner
{
public required int InnerProperty { get; set; }
}
// DbContext
public class TestDbContext : DbContext
{
public DbSet<RootEntity> RootEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Server=(localdb)\\mssqllocaldb;Database=EFCoreBugRepro;Integrated Security=true;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RootEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.OwnsOne(e => e.Outer, outer =>
{
outer.OwnsOne(o => o.Inner);
});
});
}
}
Stack traces
System.InvalidOperationException: The instance of entity type 'Inner' cannot be tracked because another instance with the same key value for {'OuterRootEntityId'} is already being tracked. When replacing owned entities, modify the properties without changing the instance or detach the previous owned entity entry first. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.OnStateChanging(EntityState newState)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationReferenceChanged(InternalEntityEntry entry, INavigationBase navigationBase, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigationBase navigationBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.LocalDetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at Program.<Main>$(String[] args) in C:\Users\fschmied\...\ReproSample\Program.cs:line 44
at Program.<Main>$(String[] args) in C:\Users\fschmied\...\ReproSample\Program.cs:line 45
Verbose output
EF Core version
10.0.7
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 10
Operating system
No response
IDE
not relevant
Bug description
Consider a scenario with:
Outer: an outer owned entity owned by the root entity, andInner: an inner (nested) owned entity owned byOuter.Further consider that a row exists in the database with a
NULLvalue in a column that maps to a non-nullable property ofOuter. At the same time, columns that map to properties ofInnerare present.This is an inconsistent state in the database,
Outeris "partially present" in the underlying data. Still, EF Core will apparently successfully load the root entity, withOuterbeing null.When you subsequently try to replace
Outerwith a (valid) new instance,SaveChangesthrows anInvalidOperationExceptionwith the messageIt's very hard to find the root cause of the problem from this exception. But if you don't trigger the exception (e.g., in a read-only code path), it's even more problematic because EF Core will pretend that
Outeris null even though there is some data for it in the database. In my opinion, EF Core should throw an exception with a clear error message upon encountering such an inconsistency while hydrating an entity.This is aggravated by EF Core scaffolding migrations that will produce this state by default (see #25359 ).
Your code
Stack traces
Verbose output
EF Core version
10.0.7
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 10
Operating system
No response
IDE
not relevant