Ef Core 2.0-删除服务器端数据后添加实体会导致异常和重复数据

时间:2018-07-03 04:09:38

标签: c# entity-framework .net-core entity-framework-core

我发现EF核心变更跟踪存在一个有趣的问题,以及它如何向数据库中添加不需要的重复数据。

操作顺序:

  1. 添加新的“订单”实体
  2. 使用对数据库的手动查询删除订单
  3. 添加另一个新的“订单”实体
  4. 引发异常:The instance of entity type 'Order' cannot be tracked because another instance with the same key value for {'OrderId'} is already being tracked.(新实体仍被添加到ChangeTracker中)
  5. 添加另一个新的“订单”实体
  6. 这次有效,但是它插入了两个新的订单,现在有重复的数据。

我不在Web应用程序中使用此服务,它是一种维护单个连接上下文的服务,因为它不断处理数据队列。但是我必须接受可以在此上下文/服务之外删除数据。

示例:

public async Task TestException()
{
    var dirtyOrders = _allOrders.Where(x => x.IsDirty).ToList();

    foreach(var order in dirtyOrders)
    {
        await CreateAsync(order);
        order.IsDirty=false;
    }

    Sql("TRUNCATE dbo.Orders;");

    dirtyOrders = _allOrders.Where(x => x.IsDirty).ToList();

    foreach(var order in dirtyOrders)
    {
        await CreateAsync(order); // exception thrown
        order.IsDirty=false;
    }

}

public async Task<Order> CreateAsync(Order order)
{
    _dbContext.OrderBook.Add(order);
    await _dbContext.SaveChangesAsync();
    return order;
}

2 个答案:

答案 0 :(得分:0)

您可以创建一个单独的方法来删除模型,以避免EF的模型生命周期和跟踪。希望它能工作。

private bool DeleteModel(Order order)
{
    try
    {
        _context.Database.ExecuteSqlCommand($"DELETE Order WHERE Id={order.Id}");
        _context.SaveChanges();
        return true;
    }
    catch(Exception ex)
    {
        return false;
    }
}


public bool SaveOrder(Order order)
{
    try
    {
        //Delete Model First
        if(DeleteModel(order))
        {
            _context.Orders.Add(order);
            _context.SaveChanges();
            return true;
        }
        else
        {
            return false;
        }
    }
    catch(Exception ex)
    {
        return false;
    }
}

答案 1 :(得分:0)

我意识到了问题所在。因为我正在使用Truncate擦除数据,所以将自动递增序列重置为1。截断后,#1订单已经在ChangeTracker中,当它要插入另一个订单时,数据库返回了主键“ 1”,已经存在于ChangeTracker中。随后的调用可能会刷新changetracker,但是您的实体仍然会以“已添加”状态坐在那里,尽管有例外,但以后调用添加该相同记录并调用SaveChanges的操作现在将插入两个重复的记录。

虽然我可以通过不重置序列来避免这种情况,但是没有一种简单的方法可以防止EF陷入这种情况。我认为行为应该是在处于“已添加”状态时覆盖Changetracker中的条目,而不是引发异常。异常不包含发生冲突的密钥,因为该密钥来自服务器,因此没有以编程方式对其进行处理的好方法。

至少,如果SaveChangesAsync()无法防止将来出现重复的行,我可以将其从changeTracking中删除。

public async Task<Order> CreateAsync(Order order)
{
    _dbContext.OrderBook.Add(order);
    try
    {
        await _dbContext.SaveChangesAsync();
    }
    catch(DbUpdateException ex)
    {
        _dbContext.OrderBook.Remove(order);
        throw ex;
    }
    return order;
}