4

我正在尝试解决使用回滚的 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);
     }
 }

堆栈跟踪如下:

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)
4

2 回答 2

3

如果您查看堆栈跟踪,您会发现无效dogMissingName记录仍然在会话的批处理插入缓冲区中徘徊,即使在第一次尝试执行插入失败之后也是如此。Dog.FindFirst()稍后在同一会话中调用会重新触发内部Flush()(再次尝试失败的插入。)

从文档的第 9.6 节

有时,ISession 会执行同步 ADO.NET 连接状态与内存中对象状态所需的 SQL 语句。这个过程,flush,默认发生在以下几点

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

此外,从文档的第 9.7.2 节

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

简单地移动using (new SessionScope()) 最外层try/catchSessionScope可能是一个可行的解决方法(初始插入将失败,引发异常catch,这将使您退出未在.) 中的 SessionScope.Flush()" 上刷新com.googlegroups.castle-project-users

或者,如果您不想关闭会话,您应该能够简单地更改会话默认刷新行为(请参阅FlushMode类),以便它永远不会刷新,除非Flush()显式调用(例如在提交之前)。请注意,管理刷新但是,这种方式会很快变得复杂且容易出错

于 2010-04-08T02:37:29.527 回答
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);
    }
}
于 2010-04-09T21:45:35.597 回答