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:
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.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.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?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.