Как Dispose () может знать, что он вызван из-за исключения? - PullRequest
8 голосов
/ 10 марта 2011

Я хотел бы написать простую единицу рабочего класса, которая будет вести себя так:

using (var unitOfWork = new UnitOfWork())
{
   // Call the data access module and don't worry about transactions.
   // Let the Unit of Work open a session, begin a transaction and then commit it.
}

Это то, что я имею до сих пор (любые комментарии будут приветствоваться, если вы считаете, что мой дизайн не так):

class UnitOfWork : IDisposable
{
   ISession _session;
   ITransation _transaction;
   .
   .
   .
   void Dispose()
   {
      _transaction.Commit();
      _session.Dispose();
   }
}

Я хотел бы откатить транзакцию в случае, если код доступа к данным вызвал какое-то исключение. Таким образом, метод Dispose() будет выглядеть примерно так:

   void Dispose()
   {
      if (Dispose was called because an exception was thrown) 
      {
         _transaction.Commit();
      }
      else
      {
         _transaction.RollBack();
      }
      _session.Dispose();
   }

Имеет ли это смысл? И если да, то как это можно сделать?

Ответы [ 5 ]

6 голосов
/ 10 марта 2011

«Dispose ()» не должен иметь ничего общего с фиксацией транзакции или откатом. Вы должны удалить свою транзакцию в вашем методе Dispose (). Изменение семантики вашего метода Dispose () только добавит путаницы в долгосрочной перспективе для вас и всех, кто использует ваш класс.

Методы транзакции Commit () и RollBack () не имеют никакого отношения к методу Dispose (), так как нет никакой корреляции между этими двумя методами и Dispose (), так как вы должны утилизировать транзакцию независимо от того, что является окончательным результат.

Это правильный шаблон для использования в отношении соединений и транзакций. Обратите внимание, что Roolback (0 относится к исключению (и не удаляет)

connection.Open();
var trasnaction = null;
try
{
  transaction = connection.BeginTransaction(); 
  ///Do Some work
  transaction.Commit();
}
catch
{
  transaction.Rollback();
}
finally
{
  if (transaction != null)
    transaction.Dispose();
  connection.Close();
}

Так имитируйте этот шаблон в вашем UnitOfWork с помощью методов Commit (), Roolback () и Dispose ().

3 голосов
/ 01 мая 2013

Немного опоздал к игре здесь, но проверьте этот пост от Ayende для (слегка сумасшедшего) решения:

В методе Dispose вам просто нужно выяснить, получилось ли оно "чисто" или нет, то есть выяснить, существует ли необработанное исключение перед совершением транзакции:

public class ExceptionDetector : IDisposable
{
    public void Dispose()
    {
        if (Marshal.GetExceptionCode()==0)
            Console.WriteLine("Completed Successfully!");
        else
            Console.WriteLine("Exception!");
    }
}
2 голосов
/ 10 марта 2011

Смысл Dispose в том, что он всегда запущен.И используя эту идиому коммита отката, вам не нужно знать разницу.

using (var unitOfWork = new UnitOfWork())
{
    // use unitOfWork here - No need to worry about transactions for this code.
    unitOfWork.Commit();
}

Здесь мы видим, что либо выдается исключение, либо unitOfWork фиксируется.Затем мы можем иметь логическое значение в UnitOfWork, отслеживающее, был ли выполнен коммит или нет.Тогда Dispose откат может не зафиксирован.Таким образом, единица работы всегда либо откатывается, либо фиксируется.

Я бы в любом случае избегал фиксации внутри Dispose.Для начала метод ITransaction.Commit обычно может выдавать исключения из-за ошибок - это совершенно нормально.Однако метод Dispose - это , который не должен выдавать исключений.Смотрите эту ссылку и ищите в Stackoverflow для получения более подробной информации о почему .

Я думаю, что-то вроде этого крупными штрихами

class UnitOfWork : IDisposable
{
   ISession _session;
   ITransation _transaction;
   bool _commitTried;

   // stuff goes here

   void Commit()
   {
      _commitTried = true;
      _transaction.Commit();
   }

   void Dispose()
   {
      if (!_commitTried) _transaction.Rollback();
      _transaction.Dispose();
      _session.Dispose();
   }
}

Проблема с полным забыванием вызова Commit, я бы сказал, не так велика, поскольку, если не зафиксировано, код клиента не будет работать , так как транзакция Rollback 'ed и изменения не применяются, которые будут обнаружены.при выполнении кода внутри прибора или вручную.

На самом деле я пытался справиться с этим в одном проекте, используя синтаксис с лямбдами, например:

_repository.InTransactionDo(ThisMethodIsRunInsideATransaction);

Таким образом, клиентам не нужно былобеспокоиться о совершении чего-либо.Я на самом деле в итоге пожалел об этом, потому что это слишком усложняло вещи, и пожалел, что ушел с вышеуказанным подходом.

2 голосов
/ 10 марта 2011

Вам нужно UnitOfWork.Commit() в конце блока using. Внутри UnitOfWork у вас есть флаг committed, который вы регистрируете в UnitOfWork.Dispose. Если флаг false там, то вы UnitOfWork.Rollback().

1 голос
/ 17 марта 2011

В конце концов, я реализовал метод, с помощью которого должны выполняться все операции:

class UnitOfWork : IDisposable
{
   ...
   public void DoInTransaction(Action<ISession> method)
   {
       Open session, begin transaction, call method, and then commit. Roll back if there was an exception.
   }
}
...