实体类型的实例无法被跟踪,因为已经有相同主键的另一个实例正在被跟踪。

179

我有一个 Service Object 叫做 Update

public bool Update(object original, object modified)
{
    var originalClient = (Client)original;
    var modifiedClient = (Client)modified;
    _context.Clients.Update(originalClient); //<-- throws the error
    _context.SaveChanges();
    //Variance checking and logging of changes between the modified and original
}

这里是我从哪里调用此方法的地方:

public IActionResult Update(DetailViewModel vm)
{
    var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
    var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
    // Changing the modifiedClient here
    _service.Update(originalClient, modifiedClient);
}

这里是GetAsNotTracking方法:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}

Fetch 方法:

public object Fetch(string id)
{
   long fetchId;
   long.TryParse(id, out fetchId);
   return GetClientQueryableObject(fetchId).FirstOrDefault();
}

GetClientQueryableObject:

private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .Include(x => x.Industry)
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType);
 }

有什么想法吗?

我查阅了以下文章/讨论,但都没有结果:ASP.NET GitHub 问题3839

更新:

这是对GetAsNoTracking的更改:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}

GetClientQueryableObjectAsNoTracking

private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .AsNoTracking()
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .AsNoTracking()
        .Include(x => x.Industry)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType)
        .AsNoTracking();
}

5
我认为你放置 AsNoTracking() 的位置可能有些晚了,因为 _context.Clients 已经被跟踪了。尝试直接在 _context.Clients 调用上放置 AsNoTracking()。除非你计划跟踪它们,否则所有的 Include 应该都使用 AsNoTracking() - Robert Harvey
我可以问一下你为什么要获取原始数据和修改后的数据吗?你为什么不只获取原始数据,进行修改,然后调用更新函数呢?我不理解获取修改后的数据的意义。 - garethb
@garethb 我需要做一个审计日志。因此,我有一个差异检查器,用于检查两个对象中的更改并将其记录到数据库中。 - Rijnhardt
1
如果您对修改后和原始数据都调用GetAsNoTracking会发生什么? - garethb
3
记录一下:只需要一个AsNoTracking()调用,无论在哪里。 - Gert Arnold
显示剩余7条评论
24个回答

-1

啊!这个问题困扰了我很久,花了很多时间进行故障排除。问题在于我的测试是并行执行的(这是XUnit的默认设置)。

为了让我的测试按顺序运行,我使用了这个属性来修饰每个类:

[Collection("Sequential")]

这是我的解决方案:串行执行单元测试(而不是并行执行)


我使用GenFu模拟我的EF内存上下文:

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();
}

在创建测试数据时,据我所推断,它在两个范围内是活动的(一次在员工的测试中,而团队测试正在运行):

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);
    }
}

为这两个测试类都添加顺序集合属性,消除了显然存在的冲突。

[Collection("Sequential")]

其他参考资料:

https://github.com/aspnet/EntityFrameworkCore/issues/7340
EF Core 2.1内存数据库不更新记录
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
在单元测试中使用EF Core SqlLite时防止跟踪问题


-1
public static void DetachEntity<T>(this DbContext dbContext, T entity, string propertyName) where T: class, new()
{
   try
   {
      var dbEntity = dbContext.Find<T>(entity.GetProperty(propertyName));
      if (dbEntity != null)
          dbContext.Entry(dbEntity).State = EntityState.Detached;
      dbContext.Entry(entity).State = EntityState.Modified;
   }
   catch (Exception)
   {
        throw;
   }
}


 public static object GetProperty<T>(this T entity, string propertyName) where T : class, new()
 {
    try
    {
        Type type = entity.GetType();
        PropertyInfo propertyInfo = type.GetProperty(propertyName);
        object value = propertyInfo.GetValue(entity);
        return value;
    }
    catch (Exception)
    {
         throw;
    }
 }

我写了这两个扩展方法,它们运行得非常好。


-1
对我来说,在导航属性中有相同的实体。
例如。
class Entity1
{
    public ICollection<Entity2> Entities2 { get; set; } // <-- problem
}

class Entity2
{
    public Entity1 Ent { get; set; } // <-- problem
}

解决方案

Entity1 e = ...
e.Entities2 = null;
db.SaveChanges();

-2
如果您的数据经常发生更改,您将注意到无法跟踪表格。例如,一些表格使用触发器更新id([key])。如果您进行跟踪,您将获取相同的id并遇到问题。

有趣,你能解释得更好吗? - Payedimaunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接