使用EF Code First锁定

时间:2013-02-21 12:10:09

标签: sql-server-2008 entity-framework concurrency ef-code-first locking

我有一张表格,里面有关于汽车的信息(我们称之为tbl_incoming_car)。该表有一个名为“customer_number”的非唯一列,显示到目前为止已进入系统的汽车数量。同一辆车可以进出很多次,但这只能登记一次。

因此,当一辆新车进入时,我需要获取最后一辆车的数量,增加它,然后将其保存为新车的“customer_number”。

我知道最简单的方法是为汽车设置一个单独的表,在那里有'customer_number',并在其他表中注册ins和out,但这只是暴露情况的一个愚蠢的例子。所以没有必要讨论这种方法是错误的,我已经知道了:))

正如我所说的,每次新车进入系统时,我都必须获得最新添加的行,获取'customer_number',增加它并将其保存为原子操作。其他应用程序实例可能会尝试执行相同操作,并且DB必须在“创建任务”期间保留对最后添加的行的请求。

我认为我可以通过将隔离级别设置为可序列化来实现,但我认为它不会阻止读取最后一行,而是插入新行。所以看起来锁定是解决方案。我已经尝试在代码中使用静态对象作为Monitor,它工作正常,但当然它仅限于相同的应用程序域,我需要在数据库级别的东西。

我认为EF中没有任何东西可以对数据库设置锁定,这对于在表上设置锁定并稍后释放它的最佳方法是什么?

感谢。

6 个答案:

答案 0 :(得分:3)

到目前为止,这是我提出的最佳方式:

    public void SetTransactionLock(String resourceName)
    {
        Ensure.IsNotNull(resourceName, "resourceName");

        String command = String.Format(
        @"declare @result int;
          EXEC @result = sp_getapplock '{0}', 'Exclusive', 'Transaction', 10000 
          IF @result < 0
            RAISERROR('ERROR: cannot get the lock [{0}] in less than 10 seconds.', 16, 1);",resourceName);

        base.Database.ExecuteSqlCommand(command);
    }

    public void ReleaseTransactionLock(String resourceName)
    {
        Ensure.IsNotNull(resourceName, "resourceName");
        String command = String.Format("EXEC sp_releaseapplock '{0}';",resourceName);
        base.Database.ExecuteSqlCommand(command);
    }

由于EF中没有内置方式,我将这两个方法添加到我的数据层,并使用它们来声明只允许一个并发操作的“临界区”。

您可以在try finally块中使用它。

答案 1 :(得分:1)

Serializable实际上解决了这个问题。 Serializable意味着事务的行为就好像它们都采用了全局数据库X锁。好像只有一个交易同时执行。

插入也是如此。将采取锁定,以防止插入错误的位置。

尽管如此,你可能无法实现死锁自由。还是值得一试。

答案 2 :(得分:1)

使用REPEATABLE READ隔离级别可以在执行SELECT命令时获取锁定。我怎么不知道SELECT命令来检索最后一行,也许它不会工作。执行SELECT的方式改变了SQL锁定行/索引/表的方式。

最好的方法是执行存储过程。 EF在数据库和应用程序之间进行了一些往返,这将增加锁定时间并影响应用程序性能。

也许你可以使用触发器在插入后进行更新。

答案 3 :(得分:1)

EF适用于C#TransactionScope

过去,我一直在解决类似的问题:

 using (var ts = new TransactionScope())
        {
            using (var db = new DbContext())
            {
                db.Set<Car>().Add(newCar);
                db.SaveChanges();
                // newCar.Id has value here, but record is locked for read until ts.Complete()

                newCar.CustomerNumber = db.Set<Car>().Max(car => car.CustomerNumber) + 1;
                db.SaveChanges();
            }
            ts.Complete();
        }

任何并发任务中的行db.Set<Car>().Max(car => car.CustomerNumber)都必须等待,因为它需要访问所有记录,您可以通过在ts.Complete()之前添加断点并尝试执行{{1}来检查代码在该行上暂停。当您恢复代码并完成ts时,查询将完成。

当然,这只适用于您的示例对您所拥有的真实场景的描述足够好,但它应该很容易适应您的需求。

有关详细信息,请参阅transactions in ef on MSDN

答案 4 :(得分:1)

此线程上前面提到的解决方案可能不普遍适用,因为EF在任何SQL操作之前运行sp_resetconnection,因此当您在DbContext上运行任何调用SQL时,它实际上释放了您应该持有的锁

我想到的是:你需要在获得锁之前克隆SqlConnection,并保持这个连接,直到你准备好发布:

public class SqlAppLock {
    DbConnection connection;
    public SqlAppLock(DbContext context, string resourceName)
    {
        ... (test arguments for null, etc)
        connection = (DbConnection)(context.Database.Connection as ICloneable).Clone();
        connection.Open();

        var cmd = connection.CreateCommand();
        cmd.CommandText = "sp_getapplock";
        ... (set up parameters)
        cmd.ExecuteNonQuery();

        int result = (int)cmd.Parameters["@result"].Value;
        if (result < 0)
        {
            throw new ApplicationException("Could not acquire lock on resource");
        }
    }

然后发布,可以使用sp_releaseapplock释放锁,或者只是Dispose()连接

答案 5 :(得分:0)

EF支持SQL timeStamp,也称为rowversion dataType。

这允许您使用乐观锁定执行更新。 如果字段被正确声明,框架会为您执行此操作。 基本上:

UPDATE,其中ID = ID SET Customer_number to X + 1 更新ID = ID和Rowversion = rowversion设置Customer_number = X + 1

因此,如果该行在线程X中加载后已更改,则会出现错误。 您可以重新加载并重试或在您的方案中采取适当的操作。

表单了解更多信息,请参阅http://msdn.microsoft.com/en-us/data/jj592904

http://msdn.microsoft.com/en-us/library/aa0416cz.aspx

和这个SO帖子

What is a good method to determine when an entities data has been changed on the database?