问题
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) withNHibernate.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