Castle.Facilities.AutoTx не запускает новую обязательную транзакцию, когда используется [Transaction(TransactionScopeOption.RequiresNew)]

Когда используешь Castle.Facilities.AutoTx средство с [Transaction(TransactionScopeOption.RequiresNew)] атрибут, ожидаемый новый System.Transactions.CommittableTransaction не создан.

Вы можете легко проверить это с помощью следующего модульного теста

using System.Transactions;
using Castle.Facilities.AutoTx.Testing;
using Castle.MicroKernel.Registration;
using Castle.Transactions;
using Castle.Windsor;
using NUnit.Framework;

namespace Castle.Facilities.AutoTx.Tests
{
    public class TransService
    {
        private readonly NewTransService _s2;

        public TransService(NewTransService s2)
        {
            _s2 = s2;
        }

        [Transaction]
        public virtual string DoInTrans()
        {
            var currentTransaction = System.Transactions.Transaction.Current;
            Assert.That(currentTransaction != null, "The current transaction mustn't be null.");
            string transId = currentTransaction.TransactionInformation.LocalIdentifier;

            _s2.DoInNewTrans(transId);

            return transId;
        }
    }

    public class NewTransService
    {
        [Transaction(TransactionScopeOption.RequiresNew)]
        public virtual string DoInNewTrans(string parentTransId)
        {
            var currentTransaction = System.Transactions.Transaction.Current;
            Assert.That(currentTransaction != null, "The current transaction mustn't be null.");
            string transId = currentTransaction.TransactionInformation.LocalIdentifier;

            Assert.AreNotEqual(parentTransId, transId, "Ambient transaction must differ from parent");

            return transId;
        }
    }

    public class SingleThread_NewAmbient
    {
        private WindsorContainer _Container;

        [SetUp]
        public void SetUp()
        {
            _Container = new WindsorContainer();
            _Container.AddFacility<AutoTxFacility>();
            _Container.Register(Component.For<TransService>());
            _Container.Register(Component.For<NewTransService>());
        }

        [TearDown]
        public void TearDown()
        {
            _Container.Dispose();
        }

        [Test]
        public void Automatically_Starts_New_CommitableTransaction()
        {
            using (var scope = new ResolveScope<TransService>(_Container))
                scope.Service.DoInTrans();
        }

    }
}

Я неправильно понимаю цель [Transaction(TransactionScopeOption.RequiresNew)] или это баг?

Я копался в Castle.Transactions исходный код, и я смог исправить поведение, изменив следующий фрагмент кода в Castle.Transactions.TransactionManager.ITransactionManager.CreateTransaction(ITransactionOptions transactionOptions):

if (activity.Count == 0)
    tx = new Transaction(new CommittableTransaction(new TransactionOptions
    ...

в

if (activity.Count == 0 || transactionOptions.Mode == TransactionScopeOption.RequiresNew)
    tx = new Transaction(new CommittableTransaction(new TransactionOptions
    ...

Может кто-нибудь из экспертов / владельцев Замка проверить это?

1 ответ

Решение

Автор здесь,

Я думаю, что ваш код великолепен и слил бы PR с ним, если бы он не нарушал другие тесты. знак равно

Причина, по которой RequNNew не очень хорошо поддерживается, заключается в том, что в 99% случаев это анти-шаблон. Вы после инкапсуляции вашей единицы работы в транзакции; и ваша единица работы должна соответствовать 1-1 бизнес-операции, которая должна быть последовательной.

Теперь, если у вас есть транзакция, идущая в текущем потоке, как в вашем случае, если вам нужно использовать "RequNew", тогда вы либо читаете грязные данные, либо порождаете несвязанную транзакцию (из бизнес-операции). перспективы). Следовательно, вы должны делать это в другой теме.

Поскольку транзакции являются "окружающими" и не явными в потоке управления на языке программирования, таком как C#, у вас остаются "слоты контекста вызова" для сохранения ссылки на транзакцию; но с точки зрения вашего кода они не существуют; у вас есть частичные функции, которые работают только в транзакционном контексте. Если вы породили вторую транзакцию; как вы собираетесь согласовать это с текущей транзакцией? Это тяжело; и может привести к проблемам.

В сторону

В других транзакционных системах, таких как geteventstore.com, вы получаете явный идентификатор транзакции - он также есть в System.Transactions, но он не является явным в API/ABI/ADO.Net для базы данных, поэтому вы не можете использовать это так же. С помощью явного идентификатора транзакции вы можете устранять ошибки, когда они становятся "сомнительными", то есть "общей проблемой"; вы не можете с System.Transactions. Вместо этого необходимо подключить транзакцию MMC к соответствующему DTC и вручную откатить ее назад или вперед.

Другие вопросы по тегам