1

我一直在尝试使用 Transaction Scope 实现 EF 上下文的悲观锁定。

楷模:

User

Room
    ICollection<User> Users
    int Capacity

控制器:

public void List()
{
    using(var context = new MyDBContext())
    {
        // Let's pretend that this method grabs list of users from room and creates list of their names and then dumbs them
        foreach(var item in context.Rooms.First().GetUserNames()) 
            Console.WriteLine(item);
    }
}

public void Join()
{
    using(var context = new MyDBContext())
    {
        var room = context.Rooms.First();
        if(room.Users.Count >= room.Capacity)
            throw new Exception("No room");

        using(var transaction = new Transaction(
                TransactionScopeOption.Required,
                new TransactionOptions
                {
                    IsolationLevel = IsolationLevel.RepeatableRead
                }
            ))
        {
            room = context.Rooms.First(); // Fetch again to get latest version

            if(room.Users.Count >= room.Capacity)
                throw new Exception("No room");

            room.Users.Add(CurrentUser);

            context.SaveChanges();
            transaction.Complete();
        }
    }
}

现在我正在寻求批准下面描述的行为应该起作用,如果它不起作用,我很高兴听到反馈或建议以使其成为可能。

预期行为:

假设我们有 3 个线程: A - 首先调用 Join();B - 调用 List(); C - 第二次调用 Join()。

房间容量为 1。

这就是我期望发生的事情:A 调用 Join()。方法检查它是否可以加入——看起来是肯定的,所以它进入了 TransactionScope。这时,B 调用 List()——因为 A 还没有完成 Join(),B 认为房间里没有用户。这里重要的是 B 不会等到 A 完成事务,而是会访问最后提交的行版本。现在,C 正在调用 Join()。因为 A 还在处理事务,所以 C 对房间的初始测试成功,所以它也进入了 TransactionScope。但是因为 A 还没有完成,所以 C 现在应该等待它。A完成交易,房间内的用户数现在为1。A解锁交易,C进入。它再次获取 Room 模型,测试容量只是为了发现它不能加入,所以抛出异常。

我的事情可能会破坏:

我不确定 IsolationLevel.RepeatableRead。在文档中,据说在事务期间可以读取但不能修改易失性数据。交易期间可以添加新数据。. 一方面,我希望这将允许 List() 方法按原样读取行并且不会等待 TransactionScope 完成。此外,它应该只锁定行,而不是表,因此可以添加新的房间。但是我担心当A还在运行时它不会阻塞锁C的TransactionScope。最后,我不确定“在事务期间可以读取但不能修改易失性数据”是否也适用于 TransactionScope。

总结一下:请告诉我我的代码是否会按预期运行,如果没有,我应该做哪些调整才能实现这一目标?提前致谢。

4

0 回答 0