I have a problem with updating entities in EF Core and then logging those changes in a table. Technologies used are:
So, I want to get an entity from the database, edit it in front-end and then update it on the backend and save those changes into the database. On top of that, I have a table called ChangeLogs
where the most important fields are From (mapped from OldValues
) and To (mapped from CurrentValues
). Well, those two fields are the same (meaning they have the exact same values, the new values) and the situation goes like this:
I get the entity from the database like this
_context.Anomalies
.Include(a => a.Asset)
.FirstOrDefaultAsync(a => a.Id == anomalyId)
Edit the entity in front-end and then make a PUT request to update it;
Update the entity:
In order for the Update()
to work, first I have to call this:
_context.DetachAllEntities();
Otherwise I get an error saying that an entity with the same Id is already being tracked. Then call Update()
and SaveChanges()
:
_context.Anomalies.Update(anomaly);
_context.SaveChanges();
The anomaly
object is the one from the request.
For the ChangeLog
part, I overridden the SaveChanges()
method, following this example, and the Old/Original and New/Current values are set like this:
auditEntry.OldValues[propertyName] = property.OriginalValue;
auditEntry.NewValues[propertyName] = property.CurrentValue;
Basically this goes through all entries from ChangeTracker
, creates an AuditEntry
and after the base.SaveChanges()
it will go back and set the EntityId for that AuditEntry because you don't have it before save changes (this is in case you add a new entity, for update nothing happens after save changes).
The Update() method is working, changes are reflected in the database. But the only problem is with ChangeLogs
, the entry from ChangeTracker.Entries()
does not know the OldValues
.
I admit that I don't fully understand the tracking system used by EF, but I suppose it should help me updating entities and not create problems. Because I know calling _context.DetachAllEntities();
is not correct. I tried to use AsNoTracking()
and drop the DetachAllEntities()
but the result seems to be the same. I thought about taking the dbEntity, copy each field from request entity to database entity and then Update(dbEntity)
but that seems like a lot of work to do for basically a small benefit. My Anomaly
entity has a lot of navigation properties, it would be difficult to create a copy method for it and to maintain it.
The DetachAllEntities()
is defined like this:
public static void DetachAllEntities(this DbContext context)
{
var entries = context.ChangeTracker.Entries().ToList();
foreach (var entry in entries)
{
entry.State = EntityState.Detached;
}
}
DbContext
is set to Scoped
lifetime, managers as well.
I tried to use the Auditing method from this tutorial as well, but the result is the same.
I have a concern that the whole Update process is not done right and therefore this problems..
UPDATE I have created a sample project to exemplify this problem. You can check the source code on bitbucket. The README hase some more info on this
I serialized the objects retrieved from the context.
With Tracking on the LEFT <====> With NO tracking on the RIGHT
Any advice, opinion, new idea, comment is welcomed. Thank you!
I think your problem is here. Because the two objects exist in different contexts, it is fundamentally a different object. If you change your code so that you retrieve the entity from the database, update its values, then use that object to pass to the SaveChangesAndAudit() method. You will get the result you are expecting. I have adjusted the code in the attached screenshot. You will also come up against a fundamental problem that most developers find, and that is mapping objects - tools like Automapper make your life easier with this sort of thing - so if your entities are big, its worth taking a look at that to help ;-)