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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With