NHibernate: How to increment a column value without concurrency issues

与世无争的帅哥 提交于 2020-01-05 06:44:25

问题


I have a database table CaseSymbol with columns: id, Symbol, LastNo

It runs over ASP.NET MVC application. Multiple application instances can get the symbol and increment LastNo.

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

Then it crashes obviously on following line:

this.context.CurrentSession.Flush();

with following exception

NHibernate.StaleObjectStateException: 'Row was updated or deleted by another transaction

I tried to wrap it in transaction and lock it like this:

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();
}

But I am getting same error, this time in the row with LockMode.Upgrade.

I tried rising transaction level to RepeatableRead, but I got following exception:

System.Data.SqlClient.SqlException: 'Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim

How can I get the application instances to wait until it is unlocked instead of crashing that the object is stale?

  • New session is generated for new thread.
  • Thread locking (lock()) the transaction helped within each application instance, but when I run two websites instances they still collide with each other (but less frequent) with NHibernate.StaleObjectStateException.

回答1:


NHibernate ISession is not thread safe.

12.2. Threads and connections You should observe the following practices when creating NHibernate Sessions:

  • Never create more than one concurrent ISession or ITransaction instance per database connection.

  • Be extremely careful when creating more than one ISession per database per transaction. The ISession itself keeps track of updates made to loaded objects, so a different ISession might see stale data.

  • The ISession is not threadsafe! Never access the same ISession in two concurrent threads. An ISession is usually only a single unit-of-work!
    Since NHibernate 5.0, the session and its queries IO bound methods have async counterparts. Each call to an async method must be awaited before further interacting with the session or its queries.

In your code, multiple threads are accessing same instance of context to Get the CaseSymbol object. In this scenario, the problem you are facing is obvious. Wrapping the things in transaction hardly help. You will start encountering other issues related to transaction and concurrency then.

Better solution is to create new context per thread. This can be easily achieved through some Factory class/method. You can refer this question to learn about it.

Also, apart from NHibernate and ISession, you need to take enough care while incrementing that variable in thread safe way. Simple googling may help.


Edit:

As you mentioned, threading issue is already taken care of.
As noted by @PanagiotisKanavos in comments, DML will be more suitable here instead of two round trips to database -- one for Get and other for flushing the changes.

With NHibernate HQL query, this can be achieved something like below:

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();


来源:https://stackoverflow.com/questions/54436281/nhibernate-how-to-increment-a-column-value-without-concurrency-issues

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!