我一直在尝试使用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()秒。
Room.Capacity为1。
这是我期待发生的事情:A调用Join()。方法检查它是否可以加入 - 看起来像是,所以它进入TransactionScope。这时,B调用List() - 因为A还没有完成Join(),B认为房间里没有用户。这里重要的是B不会等到A完成事务,但会访问最后提交的行版本。现在,C正在调用Join()。因为A仍在处理事务,C的房间初始测试成功,所以它也进入TransactionScope。 但是由于A还没有完成,C现在应该等待它。 A完成交易,房间内的用户数现在是1.解锁交易,C进入。它再次取出Room模型,测试容量只是为了发现它无法加入,所以抛出异常。
我的事情可能会破灭:
我不确定IsolationLevel.RepeatableRead。在文档中,据说易失性数据可以在事务期间读取但不能修改。可以在交易期间添加新数据。。一方面,我希望这将允许List()方法按原样读取行,并且不会等待TransactionScope完成。此外,它应该只锁定行而不是表,因此可以添加新的房间。但我担心当A仍在运行时它不会阻止锁定C的TransactionScope。最后,我不确定“交易期间是否可以读取但未修改的易失性数据”也适用于TransactionScope。
总结一下:请告诉我,我的代码是否会按预期运行,如果没有,我应该做些什么调整才能实现?提前谢谢。