【问题标题】:How I can avoid optimistic concurrency exception when I delete rows?删除行时如何避免乐观并发异常?
【发布时间】:2017-11-19 10:17:31
【问题描述】:

我有一个方法可以接收要删除的某些行的 ID。我正在使用这样的代码:

public bool delete(IEnumerable<long> paramIeId)
{
    using(myContext)
    {
        foreach(long iterator in paramIeId)
        {
            nyDbContext.Remove(new MyType(){ID = iterator});
        }
    }
}

它工作正常,因为当存在时删除行。但是如果有 1 行或更多行不存在,那么我会得到一个异常并且没有任何行被删除,尽管其中一些存在。

如果我在 T-SQL 中执行此查询,我没有问题,数据库会删除现有行并忽略不存在的行,因为最后我想删除它们,所以如果另一个进程为我删除了它们,不问题。

我可以处理刷新 dbContextfrom 数据库的乐观并发异常,但我认为这是可以避免的额外查询。

EF 有什么方法可以像 T-SQL 一样工作吗?如果我尝试删除不存在的行,请忽略它并删除其余行。

谢谢。

【问题讨论】:

  • 我不确定是否可以禁用此行为。 EF 发现您的“DELETE from”语句影响的行数少于预期并抛出此异常。在这种情况下,您最好自己执行原始 sql 查询,而不使用 EF。
  • 这是 EF 的非逻辑行为之一。有一个开放的增强请求 Optimistic concurrency check should be configurable #6218 涵盖了这一点,尽管如果您问我,删除的默认行为不应引发此类异常。
  • 如果 paramIeId 实现 IEnumerable 那么您可以执行 foreach(long id in paramIeId.ToList()) 以防止 LINQ 执行延迟查询以在运行时删除集合项。使用 .ToList() 方法将在迭代列表时冻结列表。

标签: c# optimistic-concurrency ef-core-2.0


【解决方案1】:

至少目前,在使用分离实体执行删除时,异常似乎是不可避免的。您要么必须使用 try / catch 并处理异常,要么在数据库中查询匹配的 id 并仅删除匹配项1

异常处理示例

using (myContext)
{
    foreach (long iterator in paramIeId)
    {
        nyDbContext.Remove(new MyType() { ID = iterator });
    }

    try
    {
        nyDbContext.SaveChanges()
    }
    catch(DbUpdateConcurrencyException ex)
    {
        //if you want special handling for double delete
    }
}

先查询后删除样本

请注意,我在循环之前查询了整个类型列表,以避免对每种类型进行单独的查询。

using (myContext)
{
    var existingMyTypes = nyDbContext.MyTypes.Where(x => paramIeId.Contains(x.ID));
    foreach (MyType existing in existingMyTypes)
    {
        nyDbContext.Remove(existing);
    }

    nyDbContext.SaveChanges();
}

1 注意:查询然后删除选项会留下一个可能的竞争条件,这可能会触发您尝试的OptimisticConcurrencyException - 即,如果另一个进程/线程/程序删除之间的行您自己的进程的读取和删除。完全处理这种可能性的唯一方法是处理 try / catch 中的异常。

【讨论】:

  • 但是在这种情况下,EF是如何做全部或不做的,如果只有一条不存在的记录,我必须处理异常删除仍然存在的记录。
  • @ÁlvaroGarcía 你是对的。没有内置方法可以避免在不单独执行所有删除操作的情况下重新删除并发异常。请注意,这不是第二种方法的问题(将现有类型的完整列表查询到内存中然后删除它们)。
【解决方案2】:

您无需创建新对象即可将其删除,只需让 EF 为您处理一切:

public bool delete(IEnumerable<long> paramIeId)
{
    using(var nyDbContext = new DbContext())
    {
        foreach(long id in paramIeId)
        {
            MyType myType = nyDbContext.MyTypes.FirstOrDefault(x => x.ID == id);
            if (myType != null)
            {
                nyDbContext.MyTypes.Remove(myType);
            }
        }
        nyDbContext.SaveChanges();
     }
}

【讨论】:

  • 此答案将导致paramIeId 中每个 id 的 DB 查询,这可能会显着影响性能。 OPs 代码在删除之前没有进行任何查询。
  • 问题不是关于性能,而是关于在删除之前确保记录存在,但你是对的,如果他需要批量删除很多记录,这不是最好的解决方案,只有“通用”解决方案stackoverflow.com/questions/2519866/…
  • OP 明确表示,如果不需要,他担心会做额外的查询。
  • 三个区别:1)我强调了在删除之前使用查询的权衡,2)我建议了一种基于异常处理的方法,它没有不需要的查询,3)在查询示例中,我做了整个查询 before 进入 foreach 所以它是一个单独的查询而不是许多单独的单独查询。
  • 额外的查询并不能完全消除异常——它只会降低它的可能性。有关详细信息,请参阅我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2015-09-07
  • 2015-05-07
  • 1970-01-01
相关资源
最近更新 更多