Failed Castle ActiveRecord TransactionScope приводит к тому, что будущие запросы будут недействительными - PullRequest
4 голосов
/ 29 марта 2010

Я пытаюсь решить проблему при использовании Castle ActiveRecord TransactionScope, которая откатывается.

После отката я не могу запросить таблицу Dog. Строка «Dog.FindFirst ()» завершается с ошибкой «Не удалось выполнить SlicedFindAll for Dog», поскольку не может вставить 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 ]

3 голосов
/ 08 апреля 2010

Если вы посмотрите на трассировку стека , вы поймете, что недопустимая запись dogMissingName по-прежнему висит в пакетном буфере вставки сеанса, даже после неудачной первой попытки выполнить вставку. Вызов Dog.FindFirst() позже в том же сеансе повторно вызывает внутренний Flush() (который снова пытается выполнить неудачную вставку.)

Из раздела 9.6 документации:

Время от времени ISession будет выполнить операторы SQL, необходимые для синхронизировать соединение ADO.NET состояние с состоянием объектов проводится в памяти. Этот процесс, флеш, происходит по умолчанию в следующих точках

  • из некоторых вызовов Find () или Enumerable ()
  • от NHibernate.ITransaction.Commit ()
  • от ISession.Flush ()

Дополнительно с раздел 9.7.2 документации:

Если вы откатываете транзакцию, вы должны немедленно закрыть и отменить текущую сессию чтобы убедиться, что внутреннее состояние NHibernate является согласованным.

Просто перемещение using (new SessionScope()) внутри самое внешнее try / catch может быть жизнеспособным обходным решением (первоначальная вставка не удастся, вызовет исключение, которое выведет вас из SessionScope, вероятно, вызывая второй сбой на той же вставке, сбой, который вы наконец catch - также видите "данные не сбрасываются в SessionScope.Flush ()" в com.googlegroups.castle-project-users ).

В качестве альтернативы, если вы не хотите закрывать сеанс, вы можете просто изменить поведение сброса сеанса по умолчанию (см. Класс FlushMode), чтобы он никогда не сбрасывается, если только Flush() не вызывается явно (например, перед фиксацией.) Обратите внимание, что управление сбросом таким способом очень быстро и будет усложнено и подвержено ошибкам , однако.

2 голосов
/ 10 апреля 2010

Ключ правильный на ответ Влада:

Если откатить транзакцию вы следует немедленно закрыть и выбросить текущая сессия, чтобы убедиться, что Внутреннее состояние 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);
    }
}
...