The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked

前端 未结 10 1132
一向
一向 2020-11-29 20:11

I have a Service Object Update

public bool Update(object original, object modified)
{
    var originalClient = (Client)original;
    var modifi         


        
相关标签:
10条回答
  • 2020-11-29 21:01

    Arhhh this got me and I spent a lot of time troubleshooting it. The problem was my tests were being executed in Parellel (the default with XUnit).

    To make my test run sequentially I decorated each class with this attribute:

    [Collection("Sequential")]
    

    This is how I worked it out: Execute unit tests serially (rather than in parallel)


    I mock up my EF In Memory context with GenFu:

    private void CreateTestData(TheContext dbContext)
    {
        GenFu.GenFu.Configure<Employee>()
           .Fill(q => q.EmployeeId, 3);
        var employee = GenFu.GenFu.ListOf<Employee>(1);
    
        var id = 1;
        GenFu.GenFu.Configure<Team>()
            .Fill(p => p.TeamId, () => id++).Fill(q => q.CreatedById, 3).Fill(q => q.ModifiedById, 3);
        var Teams = GenFu.GenFu.ListOf<Team>(20);
        dbContext.Team.AddRange(Teams);
    
        dbContext.SaveChanges();
    }
    

    When Creating Test Data, from what I can deduct, it was alive in two scopes (once in the Employee's Tests while the Team tests were running):

    public void Team_Index_should_return_valid_model()
    {
        using (var context = new TheContext(CreateNewContextOptions()))
        {
            //Arrange
            CreateTestData(context);
            var controller = new TeamController(context);
    
            //Act
            var actionResult = controller.Index();
    
            //Assert
            Assert.NotNull(actionResult);
            Assert.True(actionResult.Result is ViewResult);
            var model = ModelFromActionResult<List<Team>>((ActionResult)actionResult.Result);
            Assert.Equal(20, model.Count);
        }
    }
    

    Wrapping both Test Classes with this sequential collection attribute has cleared the apparent conflict.

    [Collection("Sequential")]
    

    Additional references:

    https://github.com/aspnet/EntityFrameworkCore/issues/7340
    EF Core 2.1 In memory DB not updating records
    http://www.jerriepelser.com/blog/unit-testing-aspnet5-entityframework7-inmemory-database/
    http://gunnarpeipman.com/2017/04/aspnet-core-ef-inmemory/
    https://github.com/aspnet/EntityFrameworkCore/issues/12459
    Preventing tracking issues when using EF Core SqlLite in Unit Tests

    0 讨论(0)
  • 2020-11-29 21:03

    Without overriding EF track system, you can also Detach the 'local' entry and attach your updated entry before saving :

    // 
    var local = _context.Set<YourEntity>()
        .Local
        .FirstOrDefault(entry => entry.Id.Equals(entryId));
    
    // check if local is not null 
    if (local != null)
    {
        // detach
        _context.Entry(local).State = EntityState.Detached;
    }
    // set Modified flag in your entry
    _context.Entry(entryToUpdate).State = EntityState.Modified;
    
    // save 
    _context.SaveChanges();
    

    UPDATE: To avoid code redundancy, you can do an extension method :

    public static void DetachLocal<T>(this DbContext context, T t, string entryId) 
        where T : class, IIdentifier 
    {
        var local = context.Set<T>()
            .Local
            .FirstOrDefault(entry => entry.Id.Equals(entryId));
        if (!local.IsNull())
        {
            context.Entry(local).State = EntityState.Detached;
        }
        context.Entry(t).State = EntityState.Modified;
    }
    

    My IIdentifier interface has just an Id string property.

    Whatever your Entity, you can use this method on your context :

    _context.DetachLocal(tmodel, id);
    _context.SaveChanges();
    
    0 讨论(0)
  • 2020-11-29 21:06

    I faced the same problem but the issue was very silly, By mistake I have given wrong relationship I have given relationship between 2 Ids.

    0 讨论(0)
  • 2020-11-29 21:08

    It sounds as you really just want to track the changes made to the model, not to actually keep an untracked model in memory. May I suggest an alternative approach wich will remove the problem entirely?

    EF will automticallly track changes for you. How about making use of that built in logic?

    Ovverride SaveChanges() in your DbContext.

        public override int SaveChanges()
        {
            foreach (var entry in ChangeTracker.Entries<Client>())
            {
                if (entry.State == EntityState.Modified)
                {
                    // Get the changed values.
                    var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
                    var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
                    foreach (var propName in modifiedProps)
                    {
                        var newValue = currentValues[propName];
                        //log changes
                    }
                }
            }
    
            return base.SaveChanges();
        }
    

    Good examples can be found here:

    Entity Framework 6: audit/track changes

    Implementing Audit Log / Change History with MVC & Entity Framework

    EDIT: Client can easily be changed to an interface. Let's say ITrackableEntity. This way you can centralize the logic and automatically log all changes to all entities that implement a specific interface. The interface itself doesn't have any specific properties.

        public override int SaveChanges()
        {
            foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
            {
                if (entry.State == EntityState.Modified)
                {
                    // Same code as example above.
                }
            }
    
            return base.SaveChanges();
        }
    

    Also, take a look at eranga's great suggestion to subscribe instead of actually overriding SaveChanges().

    0 讨论(0)
提交回复
热议问题