2015-04-17 138 views
1

我有一个可以被多个程序访问的SQL Server数据库。这些程序用C#编写,并使用Fluent NHibernate作为ORM。但是数据库中存在一些对象(称为令牌),根据域逻辑,不应允许多个进程/线程同时处理它们。正确处理StaleObjectStateException

我试图通过NHIbernate版本锁定实现此目的。我有以下映射

mapping.OptimisticLock.Version(); 
mapping.Version(token => token.VersionTimestamp).Generated.Always().UnsavedValue(null).Access.Property().CustomSqlType("timestamp").Nullable(); 

和下面的代码:

var token = Session.Get<Token>(tokenId); 

if (token.Status != TokenStatus.Available) 
    return new FailedResult("Token not available"); 

try 
{ 
    token.Status = TokenStatus.Locked; 
    Session.SaveOrUpdate(token); 
} 
catch (StaleObjectStateException) 
{ 
    WriteLogsToDb(); // <- another StaleObjectStateException thrown here 
    return new FailedResult("Could not acquire token"); 
} 

ProcessToken(token); // do stuff that isn't allowed to be done concurrently by multiple threads/processes 

token.Status = TokenStatus.Available; 
Session.SaveOrUpdate(token); 

return new SuccessResult(); 

的问题是,我得到一个StaleObjectStateException后,我无法保存任何东西到DB(即使是没有版本的实体映射)。我得到另一个StaleObjectStateException。但是我无法锁定令牌后,我真的需要在DB中写入日志和其他一些东西。

什么是正确的方法来做到这一点? NHibernate的这种行为的原因是什么?

回答

0

会话是UoW,可以合并您在冲洗操作期间所做的所有更改并接受它。因为在刷新期间(SaveOrUpdate没有事务执行Flush),你的对象(标记)仍然被标记为脏,并且NH在下一次刷新期间尝试保存它,并且如果它仍然锁定,你又会得到异常。

为了避免这种情况,你必须从会议中删除对象保存anythig事情之前:

try 
{ 
    token.Status = TokenStatus.Locked; 
    Session.SaveOrUpdate(token); 
} 
catch (StaleObjectStateException) 
{ 
    Session.Evict(token); // <- remove token from session 

    WriteLogsToDb(); // <- another StaleObjectStateException thrown here 
    return new FailedResult("Could not acquire token"); 
} 
+0

可悲的是,加入'Session.Evict'或'Session.Refresh'没有效果。我忘记提到'Session.SaveOrUpdate'是一个覆盖,它在内部创建一个事务,调用NHibernate的'SaveOrUpdate',然后提交事务。 – holdenmcgrohen

+0

请给我看看您正在调用WriteLogsToDb()的异常文本。和这种方法的代码。 –

+0

在关于并发性的Ayende帖子中:http://ayende.com/blog/3946/nhibernate-mapping-concurrency 我发现这个: 如果更新因为行更新而失败,我们将得到一个StaleObjectException。与所有例外情况一样,这会使会话不符合使用条件,您将不得不创建新会话来处理它._ 因此,您必须创建一个新会话才能在此情况下写入日志。 –