竞态条件导致nHibernate创建重复条目。

3

我在nHibernate上遇到了竞态条件,导致我的数据库中出现了重复条目。 不幸的是,我不能在数据库中创建一个唯一索引,因此我想仅使用nHibernate方法解决此错误。 这是一个可能在Web农场上运行的Web应用程序(因此我猜系统锁也无法解决问题)。 简化后的情况如下:

var current = UnitOfWorkManager.Instance.Current;
current.BeginTransaction(IsolationLevel.Serializable);
try {

    var myEntity = MyFactory.MyEntityRepository.GetBy(product, company);
    // race condition happens between the previous statement and Save() method.

    if (myEntity == null)
    {
      myEntity = new MyEntity();
      myEntity.Product     = product;
      myEntity.Company     = company;
      myEntity.Date        = date;
      myEntity.CurrentUser = currentUser;
      myEntity.IsManual    = true;
      myEntity.Save();
    }
    else
    {
      myEntity.IsManual    = false;
      myEntity.Save();
    }
    current.CommitTransaction();
}
catch {
    current.RollbackTransaction();
    throw;
}

我对nHibernate还不熟悉,可能会忽略一些基础知识。非常感谢您的反馈。:)


myEntity.Save(); 应该在一个事务中。 - Darren
2
你的竞态条件的本质并不是很清楚。 - Daniel Hilgarth
@DarrenDavies,这段代码包含在由UnitOfWorkManager协调的事务中(已更新代码以显示)。我甚至将隔离级别设置为Serializable,希望它会锁定整个表直到提交,但是它没有解决问题。 - Gerardo Lima
2
@GerardoLima:NHibernate是您应用程序过程的一部分。如果您无法调整数据库,它将如何帮助您? NHibernate不能治愈数据库设计中的错误决策。这就是您解决此常见问题的方法:修复您的数据库。NHibernate基本上是一个生成SQL并解释结果的库。您能否仅使用SQL实现您想要实现的目标? - Daniel Hilgarth
@BatteryBackupUnit,我猜不行...根据我在这里收到的反馈,我认为nHibernate无法容纳这种模式--虽然我不喜欢它,但我们不能因为维护任务而对应用程序重新实现很多东西。 - Gerardo Lima
显示剩余9条评论
3个回答

1
阅读nHibernate手册后,我认为你的问题可能在于如果subProjectToSupplier不为空,则第二次调用save时会执行插入操作。因为nHibernate手册中指出,“save”会执行插入操作。
尝试使用SaveOrUpdate。

在这种特定情况下,我需要将一个新行插入到数据库中或者失败。 - Gerardo Lima
SaveOrUpdate 不应该解决问题,因为它是基于 ID 的,而每个实体实例都会创建一个新的不同的 ID... - Gerardo Lima
如果您的数据重复了,那么您的保存调用之一必定有问题。如果不是第二个,则必须是第一个。您的新实体将获得您之前搜索的相同产品和公司。 - user743414
那是我的问题,@user743414,我的应用程序在Web农场上运行,以通过互联网为客户提供服务,因此我没有选择使其单线程或使用dotNet常规锁同步线程的选项。 我需要使用nHibernate进行持久化并实现某种锁定(或替代方案)来防止竞态条件。 - Gerardo Lima
我已经考虑过这个(创建一个表来保存应用程序锁),@user743414。虽然它应该解决我的问题,但我仍然认为它有点“hackish”。我真的希望nHibernate能在现实世界的互联网应用程序中工作,但它似乎缺少这个基本构建块。(请参见我在7月9日14:10的评论) - Gerardo Lima
显示剩余3条评论

0

你应该将你的Save()方法包装在一个事务中,最好实现一种模式,如IUnitOfWork或使用SessionFactory,例如:

using (var transaction = session.BeginTransaction()) {
  myEntity = new MyEntity();
  myEntity.Product = product;
  myEntity.Company = company;
  myEntity.Date = date;
  myEntity.CurrentUser = currentUser;
  myEntity.IsManual = true;
  myEntity.Save();
  transaction.Commit();
}

谢谢你,@Darren Davies,但我已经在使用UnitOfWork实现来创建事务(我更新了问题以显示它)。我的理解是似乎没有什么需要锁定的,因为没有从存储库中获取记录,所以两个线程都可以创建它们的实例... 有什么想法吗? - Gerardo Lima
@GerardoLima - 或许可以设置一个布尔值,表示正在创建一种新类型的 MyEntity,因此其他事务必须等待直到它完成。 - Darren
@Daren Davies - 这个建议看起来实际上像是创建一个“锁”。然后我遇到了问题:我应该在哪里放置锁?由于系统运行在 Web-farm 上,创建一个应用程序(或机器)级别的锁是不可行的,而且我不知道任何方法告诉 nHibernate 在数据库上放置这样的锁... - Gerardo Lima

0

好的,你在这里要求NHibernate解决方案。我不确定这是否可以100%解决你的问题,但可以尝试以下方法:

  1. 使用ReadCommitted隔离级别。
  2. 为您的需求实现一个拦截器http://nhibernate.info/doc/nh/en/#manipulatingdata-interceptors

在拦截器中,您可以执行以下操作:

  1. 查询数据库是否存在唯一列上的记录。然后,您可以应用某种合并机制
  2. 检查当前会话是否有其他类似于您的对象等待持久化。

================

另一个解决方案是在农场之间保持共享缓存(memcache),并跟踪您不希望重复的对象。

附注:我可以详细介绍每个要点。如果有什么听起来像您的解决方案,请告诉我。


其实我对nHibernate不是很有经验,也不知道什么是拦截器,所以如果您能详细解释一下这个建议,我会非常感激。 - Gerardo Lima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接