失败的城堡ActiveRecord TransactionScope导致将来的查询无效

时间:2010-03-29 08:14:37

标签: nhibernate castle-activerecord transactionscope

我正在尝试使用回滚的Castle ActiveRecord TransactionScope解决问题。

回滚后,我无法查询Dog表。 “Dog.FindFirst()”行失败,“无法对Dog执行SlicedFindAll”,因为它无法插入dogMissingName。

using (new SessionScope())
{
    try
    {
        var trans = new TransactionScope(TransactionMode.New, OnDispose.Commit);

        try 
        {
             var dog = new Dog
             {
                 Name = "Snowy"
             };
             dog.Save();
             var dogMissingName = new Dog();
             dogMissingName.Save();
        }
        catch (Exception)
        {
           trans.VoteRollBack();
           throw;
        }
        finally
        {
           trans.Dispose();
        }   
     }
     catch (Exception ex)
     {
         var dogFromDatabase = Dog.FindFirst();
         Console.WriteLine("A dog: " + dogFromDatabase.Name);
     }
 }

Stacktrace如下:

Castle.ActiveRecord.Framework.ActiveRecordException: Could not perform SlicedFindAll for Dog ---> NHibernate.Exceptions.GenericADOException: could not insert: [Mvno.Dal.Dog#219e86fa-1081-490a-92d1-9d480171fcfd][SQL: INSERT INTO Dog (Name, Id) VALUES (?, ?)] ---> System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'Name', table 'Dog'; column does not allow nulls. INSERT fails.
The statement has been terminated.
   ved System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   ved System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   ved System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   ved System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   ved System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   ved System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   ved System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   ved System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   ved System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   ved NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd)
   ved NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation)
   ved NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session)
   --- End of inner exception stack trace ---
   ved NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session)
   ved NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session)
   ved NHibernate.Action.EntityInsertAction.Execute()
   ved NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
   ved NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
   ved NHibernate.Engine.ActionQueue.ExecuteActions()
   ved NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
   ved NHibernate.Event.Default.DefaultAutoFlushEventListener.OnAutoFlush(AutoFlushEvent event)
   ved NHibernate.Impl.SessionImpl.AutoFlushIfRequired(ISet`1 querySpaces)
   ved NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results)
   ved NHibernate.Impl.CriteriaImpl.List(IList results)
   ved NHibernate.Impl.CriteriaImpl.List()
   ved Castle.ActiveRecord.ActiveRecordBase.SlicedFindAll(Type targetType, Int32 firstResult, Int32 maxResults, Order[] orders, ICriterion[] criteria)
   --- End of inner exception stack trace ---
   ved Castle.ActiveRecord.ActiveRecordBase.SlicedFindAll(Type targetType, Int32 firstResult, Int32 maxResults, Order[] orders, ICriterion[] criteria)
   ved Castle.ActiveRecord.ActiveRecordBase.FindFirst(Type targetType, Order[] orders, ICriterion[] criteria)
   ved Castle.ActiveRecord.ActiveRecordBase.FindFirst(Type targetType, ICriterion[] criteria)
   ved Castle.ActiveRecord.ActiveRecordBase`1.FindFirst(ICriterion[] criteria)

2 个答案:

答案 0 :(得分:3)

如果你查看堆栈跟踪,你会发现无效的dogMissingName记录仍然在会话的批量插入缓冲区中,即使在第一次尝试执行插入失败。稍后在同一会话中呼叫Dog.FindFirst() 会重新触发内部Flush() (再次尝试失败的插入内容。)

来自文档的section 9.6

  

ISession会不时地   执行所需的SQL语句   同步ADO.NET连接   持有对象状态的状态   在记忆中。发生此过程,冲洗   默认情况下,在以下几点

     
      
  • 来自Find()或Enumerable()
  • 的一些调用   
  • 来自NHibernate.ITransaction.Commit()
  •   
  • 来自ISession.Flush()
  •   

此外,来自文档的section 9.7.2

  

如果您回滚事务,则应立即关闭并放弃当前会话   确保NHibernate的内部状态保持一致。

只需移动using (new SessionScope()) 最外面的try / catch 可能是一种可行的解决方法(初始插入会失败,提出一个异常会导致你离开SessionScope,可能会在同一个插页上触发第二次失败,最后失败,catch - 也会在com.googlegroups.castle-project-users中看到"data is not flushed on SessionScope.Flush()" )。

或者,如果您不想关闭会话,则应该能够简单地change the session default flush behaviour (请参阅FlushMode类),以便它永远不会刷新,除非Flush()被明确调用(例如在提交之前。)请注意,以这种方式管理刷新会很快变得复杂且容易出错

答案 1 :(得分:2)

关键是弗拉德的回答:

  

如果您回滚了该交易   应立即关闭并丢弃   当前的会议,以确保   NHibernate的内部状态是   是一致的。

在您理解并应用它之后,您的代码应该如下所示:

try
{
    using (new SessionScope())
    using (var trans = new TransactionScope(TransactionMode.New, OnDispose.Commit))
    {
        try 
        {
            var dog = new Dog { Name = "Snowy" };
            dog.Save();
            var dogMissingName = new Dog();
            dogMissingName.Save();
        }
        catch (Exception)
        {
            trans.VoteRollBack();
            throw;
        }
    }
}
catch (Exception ex)
{
    using (new SessionScope())
    {
        var dogFromDatabase = Dog.FindFirst();
        Console.WriteLine("A dog: " + dogFromDatabase.Name);
    }
}