Search code examples
c#.netsql-serverentity-frameworkchange-tracking

Entity Framework: The instance of entity type '<T>' cannot be tracked because another instance with the key value '{Id: x}' is already being tracked


I have a controller which calls the ChangeActivity method, the source and destination is gained using another dbContext than what is used here. All code can be found here.

I also have a lot of unit tests which also test aginst an SQL Server database, and they do not encounter this problem.

Models

public class EntryModel
{
   public int Id { get; set; }
   public List<ActivityModel> Activities { get; set; }
   ...
}

public class ActivityModel
{
   public int Id { get; set; }
   ...
}

Problematic Code

public ActivityChangeModel ChangeActivity(ActivityModel source, ActivityModel destination, Guid userGuid)
{
    DeleteActivity(source);
    OverrideEntries(source, destination, userGuid);

    var change = AddChange(source, destination, userGuid);
    _db.SaveChanges();
    return change;
}

private void DeleteActivity(ActivityModel source)
{
    var model = _db.Activity.Single(x => x.Id == source.Id);
    model.Deleted = true;
}

private void OverrideEntries(ActivityModel source, ActivityModel destination, Guid userGuid)
{
    var entries = _db.Entry.Include(x => x.Activities).Where(x => x.Activities.Contains(source)).ToList();
    foreach (var entry in entries)
    {
        entry.Deleted = true;
    }

    foreach (var entry in entries)
    {
        var newEntry = new EntryModel(entry.StartTime, entry.EndTime, entry.RecordedTime, new List<ActivityModel>(), false, userGuid);
        _db.Entry.Add(newEntry);

        var activities = entry.Activities;
        activities.Remove(source);
        activities.Add(destination);
        newEntry.Activities = activities;
    }
}

private ActivityChangeModel AddChange(ActivityModel source, ActivityModel destination, Guid userGuid)
{
    var change = new ActivityChangeModel(source, destination, _timeService.Current, userGuid);
    _db.ActivityChange.Add(change); // Throws Error
    return change;
}

The error which is thrown is

System.InvalidOperationException: 'The instance of entity type 'ActivityModel' cannot be tracked because another instance with the key value '{Id: 5}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'

I have tried

  • Using _db.ChangeTracker.Clear(); before I add the activity to activities
  • Looking at the changes with _db.ChangeTracker.DebugView.LongView just before var activities = entry.Activities; which says that the activity Id: 5 is being tracked, but it is unchanged

Any help is much appreciated


Solution

  • The reason why I got the error is that the source and destination objects, received by ChangeActivity, are retrieved using another dbContext. This means that when destination is retrieved from the current dbContext we have two equivalent destination - one tracked by the new dbContext and another which is not.

    The fix is to either override source and destination using _db.Activity.SingleOrDefault(x => x.Id == source.Id), or (in my case) change the dbContext to be scoped instead of a transient service. Changing it to scoped means that the two destination objects we had before are now the same one, tracked by the dbContext.