3

我有一个包含汽车信息的表(我们称之为 tbl_incoming_car)。该表有一个名为“customer_number”的非唯一列,它显示了到目前为止进入系统的汽车数量。同一辆车可以进出很多次,但只注册一次。

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

我知道最简单的方法是为汽车设置一个单独的表,在其中放置“customer_number”,然后在其他表中注册输入和输出,但这只是暴露情况的一个愚蠢示例。所以没有必要讨论这种方法是错误的,我已经知道了:)

正如我所说,每次新车进入系统时,我都必须获取最新添加的行,获取“customer_number”,将其递增并将其保存为原子操作。其他应用程序实例可以尝试做同样的事情,并且数据库必须在“创建任务”期间保留对最后添加的行的请求。

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

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

谢谢。

4

6 回答 6

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 块中使用它。

于 2013-02-24T22:49:01.853 回答
1

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

对于插入也是如此。锁将采取防止插入错误位置的方式。

但是,您可能无法实现无死锁。还是值得一试的。

于 2013-02-24T23:13:52.107 回答
1

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

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

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

于 2013-03-03T00:17:29.613 回答
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() 之前添加断点并尝试Select max(CustomerNumber) from dbname.dbo.Cars在该行暂停代码时执行来检查这一点。当您恢复代码并且 ts 完成时,查询将完成。

当然,这仅适用于您的示例足以描述您所拥有的真实场景的情况,但它应该很容易适应您的需求。

有关更多信息,请参阅MSDN 上的 ef 中的事务。

于 2013-03-03T17:20:20.047 回答
1

之前在这个线程上提到的解决方案可能并不普遍适用,因为 EFsp_resetconnection在任何 SQL 操作之前运行,所以当你在 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() 连接

于 2014-06-19T18:02:11.010 回答
0

EF 支持 SQL 时间戳,也称为 rowversion 数据类型。

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

UPDATE where ID=ID SET Customer_number to X+1 变为 Update where ID=ID and Rowversion = rowversion Set Customer_number =X+1

因此,如果行在线程 X 中加载后发生了更改,则会出现错误。您可以重新加载并重试,或者采取适合您的方案的任何操作。

形成更多信息,请参阅http://msdn.microsoft.com/en-us/data/jj592904

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

和这个SO帖子

确定何时在数据库上更改实体数据的好方法是什么?

于 2013-02-21T14:37:50.727 回答