Как выполнять вложенные транзакции в NHibernate?
Могу ли я выполнять вложенные транзакции в NHibernate и как их реализовать? Я использую SQL Server 2008, поэтому поддержка определенно в СУБД.
Я нахожу, что если я попробую что-то вроде этого:
using (var outerTX = UnitOfWork.Current.BeginTransaction())
{
using (var nestedTX = UnitOfWork.Current.BeginTransaction())
{
... do stuff
nestedTX.Commit();
}
outerTX.Commit();
}
то к моменту, когда дело доходит до outerTX.Commit()
, транзакция стала неактивной и приводит к исключению ObjectDisposedException в сеансе AdoTransaction.
Итак, мы должны создавать вложенные сеансы NHibernate? Или есть какой-то другой класс, который мы должны использовать для переноса транзакций (я слышал о TransactionScope, но я не уверен, что это такое)?
Теперь я использую реализацию Ayende UnitOfWork (спасибо Sneal).
Простите любую наивность в этом вопросе, я все еще новичок в NHibernate.
Спасибо!
EDIT. Я обнаружил, что вы можете использовать TransactionScope, например:
using (var transactionScope = new TransactionScope())
{
using (var tx = UnitOfWork.Current.BeginTransaction())
{
... do stuff
tx.Commit();
}
using (var tx = UnitOfWork.Current.BeginTransaction())
{
... do stuff
tx.Commit();
}
transactionScope.Commit();
}
Однако я не так волнуюсь по этому поводу, так как он блокирует нас использованием SQL Server, а также я обнаружил, что если база данных удаленная, вам нужно беспокоиться о том, что MSDTC включен... еще один компонент, чтобы пойти не так. Вложенные транзакции настолько полезны и легко выполняются в SQL, что я вроде предположил, что NHibernate будет иметь какой-то способ эмуляции того же...
Ответы
Ответ 1
Сессии NHibernate не поддерживают вложенные транзакции.
Следующий тест всегда верен в версии 2.1.2:
var session = sessionFactory.Open();
var tx1 = session.BeginTransaction();
var tx2 = session.BeginTransaction();
Assert.AreEqual(tx1, tx2);
Вам нужно обернуть его в TransactionScope
для поддержки вложенных транзакций.
MSDTC должен быть включен или вы получите сообщение об ошибке:
{"Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool."}
Ответ 2
Я боролся с этим какое-то время. У меня будет другая трещина.
Я хочу реализовать транзакции в отдельных контейнерах-службах, потому что это делает их автономными, но затем сможет вложить кучу этих методов обслуживания в большую транзакцию и откат всей партии, если необходимо.
Потому что я использую Rhino Commons Теперь я собираюсь попробовать рефакторинг, используя With.Transaction
. В основном это позволяет нам писать код, как если бы транзакции были вложены, хотя на самом деле есть только один.
Например:
private Project CreateProject(string name)
{
var project = new Project(name);
With.Transaction(delegate
{
UnitOfWork.CurrentSession.Save(project);
});
return project;
}
private Sample CreateSample(Project project, string code)
{
var sample = new Sample(project, code);
With.Transaction(delegate
{
UnitOfWork.CurrentSession.Save(sample);
});
return sample;
}
private void Test_NoNestedTransaction()
{
var project = CreateProject("Project 1");
}
private void TestNestedTransaction()
{
using (var tx = UnitOfWork.Current.BeginTransaction())
{
try
{
var project = CreateProject("Project 6");
var sample = CreateSample(project, "SAMPLE006", true);
}
catch
{
tx.Rollback();
throw;
}
tx.Commit();
}
}
В Test_NoNestedTransaction()
мы создаем проект самостоятельно, без контекста более крупной транзакции. В этом случае в CreateSample
будет создана и зафиксирована новая транзакция или откат, если произойдет исключение.
В Test_NestedTransaction()
мы создаем образец и проект. Если что-то пойдет не так, мы хотим, чтобы оба были отброшены назад. На самом деле код в CreateSample
и CreateProject
будет работать так же, как если бы не было никаких транзакций; это полностью внешняя транзакция, которая решает, откатываться или совершать, и делает это на основании того, выбрано ли исключение. Действительно, почему я использую вручную созданную транзакцию для внешней транзакции; поэтому у меня есть контроль над тем, нужно ли совершать или откатывать, а не просто дефолтировать на-exception-rollback-else-commit.
Вы можете достичь того же самого результата без Rhino.Commons, поставив много чего такого в своем коде:
if (!UnitOfWork.Current.IsInActiveTransaction)
{
tx = UnitOfWork.Current.BeginTransaction();
}
_auditRepository.SaveNew(auditEvent);
if (tx != null)
{
tx.Commit();
}
... и так далее. Но With.Transaction
, несмотря на неуклюжесть необходимости создания анонимных делегатов, делает это довольно удобно.
Преимущество этого подхода в использовании TransactionScope
(помимо зависимости от MSDTC) заключается в том, что в окончательной транзакции внешней транзакции должен быть только один поток к базе данных, независимо от того, сколько методов было вызвано между. Другими словами, нам не нужно записывать незафиксированные данные в базу данных по мере того, как мы идем, мы всегда просто записываем ее в локальный кэш NHibernate.
Короче говоря, это решение не обеспечивает окончательного контроля над вашими транзакциями, потому что он никогда не использует больше одной транзакции. Думаю, я могу согласиться с этим, поскольку вложенные транзакции никоим образом не поддерживаются повсеместно в каждой СУБД. Но теперь, возможно, я могу хотя бы написать код, не беспокоясь о том, есть ли у нас транзакция или нет.
Ответ 3
Как предположил Сатиш, в NHibernate не поддерживаются вложенные транзакции. Я не сталкивался с ситуациями, когда нужны вложенные транзакции, но, конечно, я столкнулся с проблемами, когда мне приходилось игнорировать создание транзакций, если другие уже были активны в других подразделениях.
В приведенной ниже ссылке блога приведен пример реализации для NHibernate, но он также должен работать на SQL-сервере:
http://rajputyh.blogspot.com/2011/02/nested-transaction-handling-with.html
Ответ 4
Эта реализация не поддерживает вложенность, если вы хотите, чтобы вложенность использовала реализация Ayende UnitOfWork. Другая проблема с используемой вами реализацией (по крайней мере для веб-приложений) заключается в том, что она хранится на экземпляре ISession в статической переменной.
Я только что переписал наш UnitOfWork вчера по этим причинам, он был первоначально основан на Габриэле.
Мы не используем UnitOfWork.Current.BeginTransaction(), мы используем UnitofWork.TransactionalFlush(), который создает отдельную транзакцию в самом конце, чтобы сразу очистить все изменения.
using (var uow = UnitOfWork.Start())
{
var entity = repository.Get(1);
entity.Name = "Sneal";
uow.TransactionalFlush();
}