Home > Software design >  Error: The instance of entity type 'X' cannot be tracked because another instance with the
Error: The instance of entity type 'X' cannot be tracked because another instance with the

Time:07-06

I'm trying to load a model, Root, combining both Include/ThenInclude as well as with a stored procedure to load the model. My model is actually much more complicated than this and I'm trying to split up my Includes() with stored procedures. I'm using EF Core 5.0.

public class Root {
   public A A {get; set;}
   public B B {get; set;}
}

public class A {
    public X Data {get; set;}
   //A's other properties
}

public class B {
    public X Data {get; set;}
   //B's other properties
}

public class X {
   //X' properties
}

Part of my include looks like this. I load the data without tracking.

.Include(root => root.A).ThenInclude(a => a.Data);

As this point, root.A.Data is not null, while root.B is null. So I load the other model calling out to a stored procedure that returns multiple result sets. I use a DbReader from ADO.NET to instantiate B and X and then set root.B equal to B:

X x = new X{}; //using dbreader here from 1st result set to populate model
B b = new B{}; //using dbreader here from 2nd result set to populate model
b.Data = x;
root.B  = b;

As this point, root.A.Data and root.B.Data have the same data and are equal.

When I attach my Root model with ctx.Attach(root), where ctx is of type DbContext,

I get the following error: The instance of entity type 'X' cannot be tracked because another instance with the key value '{ID: 3}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

Is there a way to prevent this from happening? I want to attach the root because EF, as I understand, will recursively attach the navigational properties to the context. Do I need to attach the deepest child entities first and work my way up to the root? Checking if an entity is already tracked along the way before attaching it?

CodePudding user response:

Generally yes. What you are likely running into is a situation where you are loading two instances of "Data" which contain the same record. That is a no-no if you want to later re-attach entities. If you reference the same entity within a graph you want to ensure that these are managed as the same reference to a single instance of that data, not two instances that you populate from the same data. You can either do that when the data is read (provided that you aren't doing something like serializing and de-serializing that data to/from a view) or you have to do that prior to the data being re-attached.

Think of it this way: If I have an A that references a B, and that B has a reference back to that A... I could do something like:

var a = new A { Id = 1, Name = "Nick", B = new B { id = 2 } };
a.B.A = a;

Here the reference to A from B is the same single reference. If we were to attach A (and B by extension) to a DbContext, it should be fine. If "A" has a property called Name, and I run:

a.Name = "Fred";

then reading the Name through B:

var name = a.B.A.Name;

I would get "Fred"

Alternatively if I did something like this:

var a = new A { Id = 1, Name = "Nick", B = new B { id = 2 } };
a.B.A = new A { Id = 1, Name = "Nick" };

Then the same test:

a.Name = "Fred";
var name = a.B.A.Name;

I would get "Nick". These are two separate instances.

EF runs into this same issue when attaching. As it goes through "A" it attaches that instance with an Id of 1, then attaches the a.B with it's ID of 2, but then sees B has a reference to an A with Id of 1. It isn't the same instance it is already tracking so it throws. So with entities, reference equality is everything.

Detached entities are a pain to be avoided whenever possible. You should avoid code that does things like:

var orders = _context.Orders.Where(/* ... */)
    .Select(o => new Order { /* Populate a new Order entity */))
    .ToList();

If you want to detach entities, then load the entity graph, go through the graph and set their State to Detached. This ensures that at least the references to the same entities are preserved. (2 references to the same entity will reference the same single instance)

The above example using a Select is called Projection and it should only really be used to project to a non-entity such as a POCO ViewModel or DTO. This is to ensure that it isn't confused with an entity, so that when you want to take that data and put it back into the entities, you load fresh copies of tracked entities, and copy the relevant values or make the relevant additions and deletions based on the ViewModels/DTOs and then SaveChanges.

Attaching detached entities also brings the vulnerability of "stale" data where underlying records could have been changed by a different user/session between when the original data was read & detached, and when you go to save it. Often the idea to use detached entities to pass around comes from a notion that it would "save" a database trip not to have to reload the entities. Loading entities by ID is extremely fast and is pretty much required to detect and avoid stale data overwrites. (I.e. concurrency checks with a row version #)

  • Related