Skip to content

EFCore 8: DbContext saves detached entities #33557

@Daniel-Svensson

Description

@Daniel-Svensson

After having detached entities from the DbContext in EF Core 8 and made sure that they are no longer part of ChangeTracker.Entries() they will still automatically be added back later if a later object has a FK or similar with their Id.

If attaching an entity with id X, detaching it and then later attaching it a InvalidOperationException will be thrown.
If you on the other hand add a dependant/dependent on X a navigation fixup might happen against an instance which is not really part of the DbContext nor the Database...

The following problem is new in EF Core 8, it works fine with earlier version.

The behaviour was triggered by some code which generates temporary entities, attaches them to a DbContext (so that fixup of navigation properties) happens, runs some code and then detaches them (there is still some cached data in the context used for all runs). The temporary entities must sometimes reuse the same Id

Include your code

I expect the following code to work the same way as for EF Core 7, where the new object graph is attached without any problem.

// See https://aka.ms/new-console-template for more information

using System.Diagnostics;
using Microsoft.EntityFrameworkCore;

var a = new Flow { TransactionId = -2, Transaction = new Transaction { Id = -2, DealId = -3, Deal = new Deal() { Id = -3 } }};
var b = new Flow { TransactionId = -2, Transaction = new Transaction { Id = -2, DealId = -3, Deal = new Deal() { Id = -3 } } };

Connect(a);
Connect(b);

using var ctx = new SampleContext();


ctx.Flows.Add(a);


// Detach everything
foreach (var element in new object[] { a.Transaction.Deal, a.Transaction, a })
{
    ctx.Entry(element).State = EntityState.Detached;
}


//ctx.ChangeTracker.DebugView.ShortView.Dump("After Detatch");
Debug.Assert(ctx.ChangeTracker.Entries().Any() == false, "There are no entries tracked by changetracker");

/// HERE IT GOES ************BOOOOOOOOOOOOM********** InvalidOperationException 
ctx.Flows.Add(b);


void Connect(Flow f)
{
    f.TransactionId = f.Transaction!.Id;
    f.Transaction.Flows.Add(f);

    f.Transaction.DealId = f.Transaction.Deal!.Id;
    f.Transaction.Deal.Transactions.Add(f.Transaction);
}

class SampleContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseInMemoryDatabase("MyDatabase");
    }

    public virtual DbSet<Deal> Deals { get; set; }
    public virtual DbSet<Transaction> Transactions { get; set; }
    public virtual DbSet<Flow> Flows { get; set; }
}

internal class Deal
{
    public int Id { get; set; }
    public ICollection<Transaction> Transactions { get; set; } = new HashSet<Transaction>();
}

internal class Transaction
{
    public int Id { get; set; }
    public int DealId { get; set; }
    public Deal? Deal { get; set; }
    public ICollection<Flow> Flows { get; set; } = new HashSet<Flow>();
}

internal class Flow
{
    public int Id { get; set; }
    public int TransactionId { get; set; }
    public Transaction? Transaction { get; set; }
}

Include stack traces

Include the full exception message and stack trace for any exception you encounter.

Use triple-tick fences for stack traces. For example:

>	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.ThrowIdentityConflict(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 283	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(TKey key, Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry, bool updateDuplicate) Line 303	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(TKey key, Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 255	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 237	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 602	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(Microsoft.EntityFrameworkCore.EntityState oldState, Microsoft.EntityFrameworkCore.EntityState newState, bool acceptChanges, bool modifyProperties) Line 364	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(Microsoft.EntityFrameworkCore.EntityState entityState, bool acceptChanges, bool modifyProperties, Microsoft.EntityFrameworkCore.EntityState? forceStateWhenUnknownKey, Microsoft.EntityFrameworkCore.EntityState? fallbackState) Line 169	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<(Microsoft.EntityFrameworkCore.EntityState TargetState, Microsoft.EntityFrameworkCore.EntityState StoreGenTargetState, bool Force)> node) Line 125	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState> node, System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState>, bool> handleNode) Line 26	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState> node, System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState>, bool> handleNode) Line 49	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState> node, System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState>, bool> handleNode) Line 57	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState> node, System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<TState>, bool> handleNode) Line 57	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry rootEntry, Microsoft.EntityFrameworkCore.EntityState targetState, Microsoft.EntityFrameworkCore.EntityState storeGeneratedWithKeySetTargetState, bool forceStateWhenUnknownKey) Line 45	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.SetEntityState(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry, Microsoft.EntityFrameworkCore.EntityState entityState) Line 571	C#
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.Add(TEntity entity) Line 192	C#
 	[Exception] efcore_repro.dll!Program.<Main>$(string[] args) Line 29	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry rootEntry, Microsoft.EntityFrameworkCore.EntityState targetState, Microsoft.EntityFrameworkCore.EntityState storeGeneratedWithKeySetTargetState, bool forceStateWhenUnknownKey) Line 60	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<System.__Canon>.SetEntityState(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry, Microsoft.EntityFrameworkCore.EntityState entityState) Line 584	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<Flow>.Add(Flow entity) Line 194	C#
 	efcore_repro.dll!Program.<Main>$(string[] args) Line 29	C#

Include provider and version information

EF Core version: 8.0.4
Database provider: *
Target framework: .NET 8.0
Operating system: windows 11
IDE: N/A

Metadata

Metadata

Assignees

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions