C# 如何使用实体框架在读取时锁定表?

C# 如何使用实体框架在读取时锁定表?,c#,sql-server,entity-framework,multiprocessing,C#,Sql Server,Entity Framework,Multiprocessing,我有一个SQL Server(2012),我使用实体框架(4.1)访问它。 在数据库中,我有一个名为URL的表,一个独立的进程将新URL提供给该表。 URL表中的条目可以处于“新建”、“正在处理”或“已处理”状态 我需要从不同的计算机访问URL表,检查状态为“新建”的URL条目,取第一个并将其标记为“正在处理” 由于查询和更新不是原子的,我可以让两台不同的计算机读取和更新数据库中的相同URL条目 有没有办法使select then update序列原子化以避免这种冲突?我只能通过手动向表发出lo

我有一个SQL Server(2012),我使用实体框架(4.1)访问它。 在数据库中,我有一个名为URL的表,一个独立的进程将新URL提供给该表。 URL表中的条目可以处于“新建”、“正在处理”或“已处理”状态

我需要从不同的计算机访问URL表,检查状态为“新建”的URL条目,取第一个并将其标记为“正在处理”

由于查询和更新不是原子的,我可以让两台不同的计算机读取和更新数据库中的相同URL条目


有没有办法使select then update序列原子化以避免这种冲突?

我只能通过手动向表发出lock语句来实现这一点。这将完成表锁定,因此请小心!在我的例子中,它有助于创建一个队列,我不希望多个进程同时接触

using (Entities entities = new Entities())
using (TransactionScope scope = new TransactionScope())
{
    //Lock the table during this transaction
    entities.Database.ExecuteSqlCommand("SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)");

    //Do your work with the locked table here...

    //Complete the scope here to commit, otherwise it will rollback
    //The table lock will be released after we exit the TransactionScope block
    scope.Complete();
}
更新-在实体框架6中,尤其是使用
异步
/
等待
代码时,您需要以不同的方式处理事务。经过几次改装后,这对我们来说是一个巨大的打击

using (Entities entities = new Entities())
using (DbContextTransaction scope = entities.Database.BeginTransaction())
{
    //Lock the table during this transaction
    entities.Database.ExecuteSqlCommand("SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)");

    //Do your work with the locked table here...

    //Complete the scope here to commit, otherwise it will rollback
    //The table lock will be released after we exit the TransactionScope block
    scope.Commit();
}

我不能对安德烈的回答作任何评论,但我对这一评论感到担忧 “IsolationLevel.RepeatableRead将对读取的所有行应用锁,如果线程1已读取表a,且线程1未完成事务,则线程2无法从表a中读取。”

repeatable read only表示将保留所有锁,直到事务结束。当您在事务中使用此隔离级别并读取一行(比如最大值)时,将发出一个“共享”锁,并将一直保持到事务完成。此共享锁将阻止另一个线程更新该行(更新将尝试在该行上应用独占锁,该锁将被现有共享锁阻止),但它将允许另一个线程读取该值(第二个线程将在该行上放置另一个共享锁-这是允许的)(这就是为什么它们被称为共享锁的原因)。为了使上述语句正确,它需要说“IsolationLevel.RepeatableRead将对所有读取的行应用锁,如果线程1读取了表a,而线程1没有完成事务,线程2就不能更新表a。”


对于原始问题,您需要使用可重复读取隔离级别并将锁升级为独占锁,以防止两个进程读取和更新相同的值。所有解决方案都涉及将EF映射到自定义SQL(因为升级锁类型未内置到EF中),。您可以使用jocull answer,也可以使用带有输出子句的更新来锁定行(update语句始终获得独占锁,并且在2008年或更高版本中可以返回结果集)。

@jocull提供的答案非常好。我提供了以下调整:

与此相反:

"SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)"
这样做:

"SELECT TOP 0 NULL FROM MyTable WITH (TABLOCKX)"

这是一种更通用的方法。您可以创建一个只将表名作为参数的帮助器方法。不需要知道数据(即任何列名),也不需要实际检索管道中的记录(即
TOP 1

您可以尝试向数据库传递UPDLOCK提示,只锁定特定行。这样,它选择更新的内容也会获得独占锁定,以便保存更改(而不是在开始时获取readlock,稍后在保存时尝试升级).上面jocull建议的Holdlock也是一个好主意

private static TestEntity GetFirstEntity(Context context) {
return context.TestEntities
          .SqlQuery("SELECT TOP 1 Id, Value FROM TestEntities WITH (UPDLOCK)")
          .Single();
}

我强烈建议考虑乐观并发:

这是一个非常好的解决方案。它比使用IsolationLevel的其他解决方案工作得更好(使用它们时,我遇到了多址访问的问题)在我的情况下,我必须确保一次只能有一个线程访问一个表。即使使用此解决方案,当两个线程a和B同时执行时,它们都被锁定。此方案有解决方案吗?@SQL.netarrior您是说出现了死锁吗?这意味着是一个互斥锁,因此一个线程应该锁定其他线程r、 但完成后再释放。我还没有遇到死锁,但在查询时请小心不要打开此上下文中的另一个上下文。由于每次锁定的方式不完全相同,我在意外情况下创建了死锁。最好一次只对一个表进行操作。当您唯一的方法是通过这些选择时,这会起作用使用TABLOCKXs。它不会在没有提示的情况下阻止您可能在应用程序的其他位置或SQL Mng Studio或其他地方使用的选择。您的异步问题可能与
TransactionScope
默认选项有关,该选项不流动
SynchronizationContext
。解决方案:
new TransactionScope(scopeOption、transactionOption、TransactionScopeAsyncFlowOption.Enabled);
为什么省略
holdlock
?!没有它,独占锁在语句完成后释放!嗨,阿里。锁在整个EF事务中保持活动状态。我们在我的工作中经常在生产中使用它。你确实需要在事务中。这很酷,我不知道你可以这样选择。我想知道是否有办法要参数化表名以安全地使其成为通用方法?
private static TestEntity GetFirstEntity(Context context) {
return context.TestEntities
          .SqlQuery("SELECT TOP 1 Id, Value FROM TestEntities WITH (UPDLOCK)")
          .Single();
}