What can cause persisted entities from a DbSet to be detached?

c# ef-core-2.2 entity-framework-core

Question

I'm using Entity Framework Core to retrieve entities already stored in the database, but depending on how I do that, they are sometimes retrieved in the "Detached" state, even when I'm not using AsNoTracking at all.

These are the classes used to model the database:

class AppDbContext : DbContext
{
    public DbSet<Thing> Thing { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer("...");
    }
}

class Thing
{
    public int ThingId { get; set; }
    public string Name { get; set; }
}

The following is a class used to reproduce the scenario where the entities are retrieved in a detached state:

class Wrapper
{
    public Thing Thing { get; set; }
    public Wrapper(Thing t)
    {
        Thing = t;
    }
}

The main program then does the following:

foreach (var wrapper in context.Thing.Select(a => new Wrapper(a)))
{
    Console.WriteLine(context.Entry(wrapper.Thing).State);
}

foreach (var thing in context.Thing.Select(a => a))
{
    Console.WriteLine(context.Entry(thing).State);
}

Assuming there are three rows in the Thing table, the output becomes the following:

Detached
Detached
Detached
Unchanged
Unchanged
Unchanged

In other words, the entities are detached if retrieved and then passed into the Wrapper constructor but tracked (in the "Unchanged" state) if simply retrieved regularly.

It is my understanding that entities already persisted to the database should always be retrieved in a tracked state unless explicitly retrieved with AsNoTracking, so what could cause this difference in behavior? And how could it be fixed to make sure the entities are always tracked?

A few notes:

  • The Wrapper class is clearly pointless here, but it's a minimal example of a more meaningful construct in my real program that causes the same behavior.
  • Flipping the order of the foreach loops (so that the one with the wrapper runs last) causes the entities to be tracked in both loops, so in that case the first loop clearly has a side effect on the second loop.
  • Extending the first foreach loop to iterate over context.Thing.ToArray().Select(a => new Wrapper(a)) (with a ToArray added) gives the expected result (tracked entities), so this seems to be related to the method of iteration - but how?
1
3
2/15/2019 1:34:19 PM

Accepted Answer

Apparently, the EF code interprets Select(a => new Wrapper(a)) the same as Select(a => new { a.Id, a.Name } ). It cannot see that a Wrapper stores a back reference.

In other words, it sees (thinks) you are immediately converting the entity so it decides not to track it.

It is specified here, but you have to understand that the new{} part is also processed by EF. And your new Wrapper(a) is not.

You could try a => new Wrapper() {Thing = a}, I'm not 100% sure about that.

... the first loop clearly has a side effect on the second loop.

Yes, as long as they are part of the same connection. The tracker won't 'forget' entities. You can read about that here.

2
2/19/2019 2:37:56 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow