我一直在尝试使用 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。
总结一下:请告诉我我的代码是否会按预期运行,如果没有,我应该做哪些调整才能实现这一目标?提前致谢。