下面的场景,我认为是很常见的,虽然我知道解决它的一种方法,但它缺乏优雅。
我给出的例子是基于https://github.com/sharparchitecture/Sharp-Architecture-Cookbook。
我正在编写的应用程序是ASP。NET MVC应用程序,并且必须支持多个用户在同一个对象上工作。
下面的场景是一个边缘情况,但仍然是有效的。
假设您有两个用户在同一个对象上工作,并且dB行是否可以更新取决于特定字段的值。为了使其更具体,假设您有一个Product,为了保持简单,这个Product有"Name"one_answers"QuantityInStock"字段。
假设最初,Product有10项,User1和User2想购买该产品。当两个用户都看到初始表单时,他们被告知库存中有10种这种商品。现在User1买了所有10件商品,而User2去喝咖啡。所以User1的事务没有问题。
然后User2喝完咖啡回来,他相信还有10件存货。所以他想买1件,但他必须阻止这样做,因为没有库存。
所以这个问题可以用ASP来解决。. NET DataAnnotations验证,这将捕获大多数情况。然而,在我们的边缘情况下,假设User1和User2执行相同的操作,但在几分之一秒内,当User2提交表单时,它通过ASP。. NET验证,但是当它到达持久层时,QuantityInStock为0。
解决这个问题的方法是尽可能在最近的时刻执行验证,即在调用Update方法之前。
现在来看一些代码。
public ProductModel CreateOrUpdate(ProductModel productModel)
{
var currentProductModel = Get(productModel.Id);
var currentQuantityInStock = currentProductModel.QuantityInStock;
if(currentProductModel.QuantityInStock !=0 && productModel.QuantityInStock >= currentQuantityInStock )
{
currentProductModel.QuantityInStock= productModel.QuantityInStock;
currentProductModel.Name = productModel.Name;
this.productModelRepository.SaveOrUpdate( currentProductModel );
return productModel;
}
else
{
//Raise an exception
}
}
现在,我调用的事实是:
var currentProductModel = Get(productModel.Id);
意味着如果我要这样做:
this.productModelRepository.SaveOrUpdate( productModel );
将导致异常:
一个具有相同标识符值的不同对象已经与会话关联:1
因此,我必须将所有的值从productModel复制到currentProductModel。当使用像Automapper这样的东西时,这很好,但我仍然觉得有点不对劲,因为我觉得我应该能够保存productModel原样,而不必将数据从一个对象传输到另一个对象。
此外,必须进行两次相同的验证,一次使用DataAnnotation,另一次在更新之前进行,违反了DRY原则。
关键是我觉得我错过了一个技巧,但不知道从哪里开始,该调查什么。
这对我来说是一个简单的问题,但想出一个漂亮优雅的解决方案是另一回事。那么问题是你过去是如何处理这种简单的情况的?我是不是想多了?
您尝试过版本乐观锁定吗?
// Fluent mapping
public EntitiyMap()
{
OptimisticLock.All(); // all properties musn't be changed in db when saving
// or
OptimisticLock.Dirty(); // only dirty properties musn't be changed in db when saving
}
//
public ProductModel CreateOrUpdate(ProductModel productModel)
{
try
{
// productModel is already validated and updated
this.productModelRepository.SaveOrUpdate( productModel );
return productModel;
}
catch (StaleObjectException)
{
// somebody changed the object in database after we have read it
// Raise an exception or whatever
}
}
更新:我以另一种方式处理这些事情
public void BuySomething(ProductModel productModel, int amount)
{
int tries = 5;
bool success = false;
while(!success && tries > 0)
{
if (productModel.QuantityInStock <= amount)
{
//Raise an exception
}
productModel.QuantityInStock - amount;
try
{
this.productModelRepository.SaveOrUpdate( productModel );
}
catch (StaleObjectException)
{
// somebody changed the object in database after we have read it
this.productModelRepository.Refresh(productModel);
tries--;
}
}
if (tries <= 0)
{
// Raise an exception or whatever
}
}
如果其间没有人更改,则零额外往返,并保证事务的序列化