.net-coreentity-framework-coreentitieschange-tracking

Removing (clearing) specific entries from EF Core ChangeTracker


.NET 7/ EF Core 7: we have a process that tracks changes to a db and I have no control over it. It's a black box.

What I do have control over is the methods to save these changes. In my routine I want to omit entries that are NOT from two core tables. Is this a good way to "clear" those entries from the tracker? Or is there a more accepted way to do it?

Our database is fairly small with each table holding < 10k records.

// first we only want to commit changes to Schedule and Vacation ONLY so get those entries which are not part of that set.
var unDbSets = context.ChangeTracker.Entries()
    .Where(e => e.Entity is not Schedule && e.Entity is not Vacation)
    .Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified)
    .ToList();

foreach (var unDbSet in unDbSets)
{
    if (unDbSet.State == EntityState.Added)
    {
        // entity was never in the DB so we just try detaching it. this way EF will stop tracking it and will not take any
        // actions to serialize changes against DB.
        // note: we had this originally to Unchanged but that throws an exception
        unDbSet.State = EntityState.Detached;
    }
    else
    {
        // entity was in the DB so we just change the state to unchanged and it will not do anything with it and EF will leave
        // it in its original state in the DB.
        unDbSet.State = EntityState.Unchanged;
    }
}

// finally save the tracker changes
context.SaveChanges();

Solution

  • That is about how to set up a safeguard with the DbContext to avoid non-allowed edits. A variation I have used a marker interface to mark what entities are allowed to be updated:

    I.e.

    public interface IEditableEntity {}
    

    This can be tied to any base entity class you might have to centralize properties like LastModifiedBy and LastModifiedDateTime etc. if you incorporate handling those within the DbContext. Then to deal with rejecting edits or inserts etc. I tend to deal with these individually as I might want to report on details about what kind of changes are being discarded.

    var unAllowedUpdates = ChangeTracker.Entries().Any(x => x.State == EntityState.Modified
        && !typeof(IEditableEntity).IsAssignableFrom(x.GetType())));
    

    Ultimately handling the removal of those items from the change tracker is what you have done, by detaching adds and setting modified to unchanged. However, if you want to fully discard those changes you should also call ChangeTracker.Clear() after the SaveChanges() is completed if additional queries could be run against the DbContext instance after saving. For instance say I have a Trip entity that has these Schedules and for whatever reason the Trip can be updated that I don't want persisted, but I do want to persist additions and updates to the Schedules. When saving the data I can set Trip to Unchanged so those changes don't get written, but if the code happens to load the trip again from the DbContext, it will still receive my modified (but Unchanged) Trip entity in the tracking cache rather than pulling the real data state. If you intend to discard changes but still are Ok to see possible modifications after Saving (that were not actually persisted) then you don't want to clear the change tracker, but if you want to ensure discarded changes are reflected in any further calls from that DbContext instance then you should clear the change tracker after saving if changes were discarded/ignored.