我有一个数据库表 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} }。答案 0 :(得分:0)
NHibernate ISession
不是thread safe。
12.2。螺纹和连接 创建NHibernate会话时,应遵循以下做法:
每个数据库连接永远不要创建多个并发的ISession或ITransaction实例。
在每个事务的每个数据库中创建多个ISession时要格外小心。 ISession本身会跟踪对已加载对象所做的更新,因此不同的ISession可能会看到陈旧的数据。
ISession不是线程安全的!切勿在两个并发线程中访问同一ISession。 ISession通常只是一个工作单元!
从NHibernate 5.0开始,会话及其查询IO绑定方法具有异步对应对象。在与会话或其查询进行进一步交互之前,必须等待对异步方法的每次调用。
在您的代码中,多个线程正在访问context
对象的Get
到CaseSymbol
的同一实例。在这种情况下,您面临的问题是显而易见的。在事务中包装东西几乎没有帮助。然后,您将开始遇到与事务和并发相关的其他问题。
更好的解决方案是为每个线程创建新的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();