gcs*_*cso 5 c# sql-server nhibernate concurrency
我正在使用NHibernate和version属性,每次更新聚合根时都会自动递增.如果2个或更多人在同一时间更新同一记录会怎样?
另外,我该怎么测试呢?
请注意,这不是我所处的情况,只是想知道.
正如其他人所说,SQL Server中的更新是原子操作.但是,在使用NHibernate(或任何O/RM)更新数据时,通常首先select是数据,对对象进行更改,然后update对数据库进行更改.这一系列事件不是原子的.即使选择和更新在彼此之间的毫秒内执行,也存在另一次更新在中间滑动的可能性.如果两个客户端获取相同版本的相同数据,如果他们认为他们是当时唯一编辑该数据的人,他们可能会无意中覆盖彼此的其他更改.
如果我们没有防范这种并发更新的情况,可能会发生奇怪的事情 - 看似不可能的偷偷摸摸的错误.假设我们有一个模拟水的状态变化的类:
public class BodyOfWater
{
public virtual int Id { get; set; }
public virtual StateOfMatter State { get; set; }
public virtual void Freeze()
{
if (State != StateOfMatter.Liquid)
throw new InvalidOperationException("You cannot freeze a " + State + "!");
State = StateOfMatter.Solid;
}
public virtual void Boil()
{
if (State != StateOfMatter.Liquid)
throw new InvalidOperationException("You cannot boil a " + State + "!");
State = StateOfMatter.Gas;
}
}
Run Code Online (Sandbox Code Playgroud)
假设数据库中记录了以下水体:
new BodyOfWater
{
Id = 1,
State = StateOfMatter.Liquid
};
Run Code Online (Sandbox Code Playgroud)
两个用户几乎同时从数据库中获取此记录,对其进行修改,然后将更改保存回数据库.用户A冻结水:
using (var transaction = sessionA.BeginTransaction())
{
var water = sessionA.Get<BodyOfWater>(1);
water.Freeze();
sessionA.Update(water);
// Same point in time as the line indicated below...
transaction.Commit();
}
Run Code Online (Sandbox Code Playgroud)
用户B试图煮水(现在是冰!)......
using (var transaction = sessionB.BeginTransaction())
{
var water = sessionB.Get<BodyOfWater>(1);
// ... Same point in time as the line indicated above.
water.Boil();
sessionB.Update(water);
transaction.Commit();
}
Run Code Online (Sandbox Code Playgroud)
......并且成功了!!! 什么?用户A冻结了水.不应该抛出一个例外,说"你不能煮沸!"?用户B 在用户A保存他的更改之前获取了数据,因此对于两个用户来说,水似乎最初是流动的,因此允许两个用户保存他们的冲突状态更改.
为了防止出现这种情况,我们可以Version在类中添加一个属性,并在NHibernate中映射它<version />:
public virtual int Version { get; set; }
Run Code Online (Sandbox Code Playgroud)
这只是一个NHibernate每次更新记录时都会增加的数字,它会检查以确保在我们不看的时候没有其他人增加了版本.而不是像并发一样的并发sql更新...
update BodyOfWater set State = 'Gas' where Id = 1;
Run Code Online (Sandbox Code Playgroud)
... NHibernate现在将使用更智能的查询:
update BodyOfWater set State = 'Gas', Version = 2 where Id = 1 and Version = 1;
Run Code Online (Sandbox Code Playgroud)
如果受查询影响的行数为0,则NHibernate知道出现了问题 - 其他人更新了行以使版本号现在不正确,或者有人删除了行以使该ID不再存在.然后NHibernate会抛出一个StaleObjectStateException.
select数据初始化与后续数据之间的时间越长update,此类并发问题的可能性就越大.考虑Web应用程序中的典型"编辑"表单.从数据库中选择实体的现有数据,放入HTML表单并发送到浏览器.在将表单中的值发送回服务器之前,用户可能需要花费几分钟时间修改表单中的值.可能有其他人同时编辑相同信息的机会,并且他们在我们之前保存了他们的更改.
在这样的场景中,确保版本在几毫秒内没有改变我们实际上保存更改可能是不够的.要解决此问题,您可以将版本号作为隐藏字段与其余表单字段一起发送到浏览器,然后检查以确保在保存之前从数据库中取回实体时版本未更改.此外,您可以通过提供单独的"视图"和"编辑"视图来限制初始select和最终之间的时间量,update而不是仅使用"编辑"视图.用户花在"编辑"视图上的时间越少,他们就会越少有机会收到一条恼人的错误消息,说明他们的更改无法保存.