NHibernate:如何增加列值而没有并发问题

时间:2019-01-30 08:34:09

标签: c# .net nhibernate

我有一个数据库表 CaseSymbol ,其列为: id Symbol LastNo

它在ASP.NET MVC应用程序上运行。多个应用程序实例可以获取符号并递增 LastNo

 CaseSymbol symbol = this.context.CurrentSession.Get<CaseSymbol>(id);
 symbol.LastNo++;

然后它显然在以下行崩溃:

this.context.CurrentSession.Flush();

有以下例外情况

  

NHibernate.StaleObjectStateException:'行已被另一个事务更新或删除

我试图将其包装在事务中并像这样锁定它:

using (ITransaction transaction = this.context.CurrentSession.BeginTransaction())
{
    CaseSymbol symbol = this.context.CurrentSession.Get<CaseSymbol>(id, LockMode.Upgrade);
    symbol.LastNo++;
    this.context.CurrentSession.Flush();
    transaction.Commit();
}

但是我在LockMode.Upgrade的行中遇到了同样的错误。

我尝试将交易级别提高到RepeatableRead,但遇到以下异常:

  

System.Data.SqlClient.SqlException:'事务(进程ID 57)与另一个进程在锁资源上死锁,并且被选择为死锁受害者

如何使应用程序实例等待直到其解锁,而不是使对象失效?

  • 为新线程生成了新会话。
  • 线程锁定(lock())在每个应用程序实例中对事务进行了帮助,但是当我运行两个网站实例时,它们仍然与{{1} }。

1 个答案:

答案 0 :(得分:0)

NHibernate ISession不是thread safe

  

12.2。螺纹和连接   创建NHibernate会话时,应遵循以下做法:

     
      
  • 每个数据库连接永远不要创建多个并发的ISession或ITransaction实例。

  •   
  • 在每个事务的每个数据库中创建多个ISession时要格外小心。 ISession本身会跟踪对已加载对象所做的更新,因此不同的ISession可能会看到陈旧的数据。

  •   
  • ISession不是线程安全的!切勿在两个并发线程中访问同一ISession。 ISession通常只是一个工作单元!
      从NHibernate 5.0开始,会话及其查询IO绑定方法具有异步对应对象。在与会话或其查询进行进一步交互之前,必须等待对异步方法的每次调用。

  •   

在您的代码中,多个线程正在访问context对象的GetCaseSymbol的同一实例。在这种情况下,您面临的问题是显而易见的。在事务中包装东西几乎没有帮助。然后,您将开始遇到与事务和并发相关的其他问题。

更好的解决方案是为每个线程创建新的context。通过某些Factory类/方法可以轻松实现。您可以参考this问题来了解它。

此外,除了NHibernate和ISession,在以线程安全的方式递增该变量时,您还需要格外小心。简单的谷歌搜索可能会有所帮助。


编辑:

您已经提到,线程问题已得到解决。
正如@PanagiotisKanavos在评论中指出的那样,DML在这里比在数据库中进行两次往返更合适-一次用于Get,另一次用于刷新更改。

使用NHibernate HQL查询,可以通过以下方式实现:

var hql = string.Format("UPDATE {0} C SET C.LastNo = C.LastNo + 1 WHERE C.ID = :id, typeof(CaseSymbol));
this.context.CurrentSession.CreateQuery(hql)
   .SetParameter("id", id)
   .ExecuteUpdate();