以下情况是,我会说非常普遍,虽然我知道解决它的一种方法,但它缺乏优雅。
我给出的示例基于https://github.com/sharparchitecture/Sharp-Architecture-Cookbook。
我编码的应用程序是一个ASP.NET MVC应用程序,必须支持多个用户在同一个对象上工作。
以下情况是边缘情况,但仍然是有效情况。
假设您有两个用户在同一个对象上工作,并且dB行是否可以更新取决于特定字段的值。为了使它更具体,让我们说你有一个产品,为了简单起见,这个产品有"名称"和" QuantityInStock"领域。
首先说,产品有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原则。
关键是我觉得我错过了一个技巧,但我不知道从哪里开始以及要调查什么。
这对我来说是一个简单的问题,但提出一个很好的优雅解决方案是别的。那么问题是你过去如何处理这个简单的案例?我是否想过这个?
答案 0 :(得分:0)
你试过乐观锁定版本吗?
// 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
}
}
如果没有人在其间进行任何更改,那么零额外往返,并保证事务的序列化