gpt4 book ai didi

.net - 在 Entity Framework 中防止if-exists-update-else-insert的竞争条件

转载 作者:行者123 更新时间:2023-12-03 07:27:37 25 4
gpt4 key购买 nike

我一直在阅读有关如何在EF中实现if-exists-insert-else-update语义的其他问题,但是我不理解答案的工作方式,或者实际上它们没有解决问题。提供的常见解决方案是将工作包装在事务范围内(例如:Implementing if-not-exists-insert using Entity Framework without race conditions):

using (var scope = new TransactionScope()) // default isolation level is serializable
using(var context = new MyEntities())
{
var user = context.Users.SingleOrDefault(u => u.Id == userId); // *
if (user != null)
{
// update the user
user.property = newProperty;
context.SaveChanges();
}
else
{
user = new User
{
// etc
};
context.Users.AddObject(user);
context.SaveChanges();
}
}

但是我看不到这如何解决任何问题,因为要正常工作,如果第二个线程尝试访问相同的用户ID,则上面加注星标的行应该阻塞,仅在第一个线程完成工作时才阻塞。但是,使用事务不会导致这种情况,并且由于第二个线程尝试第二次创建同一用户时发生键冲突,我们将抛出UpdateException。

与其捕获由竞争条件引起的异常,不如首先防止竞争条件发生,这会更好。一种实现方法是,加星号的行在与其条件匹配的数据库行上获取排他锁,这意味着在此块的上下文中,一次只能有一个线程与用户一起使用。

对于EF用户而言,这似乎是一个普遍的问题,因此我正在寻找一种可以在任何地方使用的干净通用的解决方案。

如果可能的话,我真的想避免使用存储过程来创建我的用户。

有任何想法吗?

编辑:我尝试使用相同的用户ID在两个不同的线程上同时执行上述代码,尽管进行了可序列化的事务,但它们都能够同时进入关键部分(*)。当第二个线程尝试插入第一个线程刚刚插入的用户ID时,这将引发UpdateException。这是因为,如下面的Ladislav所指出的,可序列化的事务仅在开始修改数据而不是读取之后才获取排他锁。

最佳答案

使用可序列化事务时,SQL Server会在读取记录/表上发出共享锁。共享锁不允许其他事务修改锁定的数据(事务将阻止),但允许其他事务在发出锁的事务开始修改数据之前读取数据。这就是该示例不起作用的原因-共享锁允许并发读取,直到第一个事务开始修改数据为止。

您需要隔离,其中select命令专门为单个客户端锁定整个表。它必须锁定整个表,因为否则将无法解决插入“相同”记录的并发问题。使用提示时,可以通过选择命令对记录或表进行锁定的细粒度控制,但您必须编写直接SQL查询才能使用它们-EF不支持此功能。我描述了独占锁定该表here的方法,但这就像创建对该表的顺序访问一样,它会影响所有其他访问该表的客户端。

如果您确实确定此操作仅在单一方法中发生,并且没有其他应用程序在使用数据库,则可以将代码放入关键部分(例如.NET与lock同步),并确保在.NET端仅单线程可以访问关键部分。那不是那么可靠的解决方案,但是任何使用锁和事务级别的方法都会对数据库性能和吞吐量产生很大影响。您可以将此方法与乐观并发(唯一约束,时间戳等)结合使用。

关于.net - 在 Entity Framework 中防止if-exists-update-else-insert的竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6171588/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com