我如何在删除行时避免乐观并发异常?

4
我有一个方法,可以接收要删除的一些行的ID。我正在使用类似以下代码的方式:
public bool delete(IEnumerable<long> paramIeId)
{
    using(myContext)
    {
        foreach(long iterator in paramIeId)
        {
            nyDbContext.Remove(new MyType(){ID = iterator});
        }
    }
}

因为删除操作会删掉存在的行,所以它能够正常工作。但是,如果有一个或多个行不存在,那么我就会遇到异常,并且没有一行被删除,尽管其中一些行存在。
如果我在T-SQL中执行此查询,我不会遇到问题,因为数据库会删除现有的行并忽略不存在的行,因为最终我要删除它们,所以如果另一个进程为我删除了它们,那就没有问题。
我可以通过从数据库刷新dbContext来处理乐观并发异常,但我认为这样做会进行额外的查询,而这些查询可以避免。
是否有任何方法使EF像T-SQL一样工作?如果我尝试删除不存在的行,则忽略它并删除其余的行。
谢谢。

我不确定是否有可能禁用此行为。EF看到您的“DELETE from”语句影响的行数少于预期,并抛出此异常。在这种情况下,最好自己执行原始的SQL查询,而不使用EF。 - Evk
这是 EF 的一个非逻辑性行为之一。有一个开放的增强请求 [Optimistic concurrency check should be configurable #6218] (https://github.com/aspnet/EntityFrameworkCore/issues/6218)覆盖了这一点,尽管如果你问我,删除的默认行为不应该抛出这样的异常。 - Ivan Stoev
如果paramIeId实现了IEnumerable<T>,那么你可以使用foreach(long id in paramIeId.ToList())来防止LINQ在运行时执行延迟查询以删除集合项。使用.ToList()方法将在迭代期间冻结列表。 - Kunal Mukherjee
2个回答

2

目前来说,使用分离实体执行删除操作似乎无法避免异常。你可以使用 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如何全有或全无,我必须处理异常以删除仍然存在的记录。 - Álvaro García
@ÁlvaroGarcía 你说得对。没有内置的方法可以避免在并发异常上重新删除而不必单独执行所有删除操作。请注意,这不是第二种方法的问题(将现有类型的完整列表查询到内存中然后删除它们)。 - just.another.programmer

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

1
这个答案将针对paramIeId中的每个id生成DB 查询,这可能会显著影响性能。在执行删除操作之前,OP的代码没有执行任何查询。 - just.another.programmer
这个问题不是关于性能,而是要确保在删除记录之前该记录存在,但你是对的,如果他需要批量删除很多记录,这不是最好的解决方案,只是“通用”解决方案。https://dev59.com/HnE85IYBdhLWcg3w_Itr - Isma
OP特别表示他担心如果不需要的话会进行额外的查询。 - just.another.programmer
三个不同之处:1)我强调了在删除之前使用查询的权衡,2)我建议采用基于异常处理的方法,该方法没有不必要的查询,3)在查询示例中,我在进入foreach之前完成了整个查询,因此它是一个单一的查询,而不是许多单独的查询。 - just.another.programmer
额外的查询并不能完全消除异常 - 它只是使其发生的可能性大大降低。请查看我的答案以获取更多详细信息。 - just.another.programmer

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