我发现EF核心变更跟踪存在一个有趣的问题,以及它如何向数据库中添加不需要的重复数据。
操作顺序:
The instance of entity type 'Order' cannot be tracked because another instance with the same key value for {'OrderId'} is already being tracked.
(新实体仍被添加到ChangeTracker中)我不在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;
}
答案 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;
}