.NET 7 分布式事务问题

问题描述 投票:0回答:4

我正在开发小型 POC 应用程序来测试 .NET7 对分布式事务的支持,因为这是我们工作流程中非常重要的方面。

到目前为止我一直无法让它工作,我不知道为什么。在我看来,要么是 .NET7 中存在某种错误,要么是我遗漏了一些东西。

简而言之,POC 非常简单,它运行 WorkerService,它做了两件事:

  1. 保存到“业务数据库”
  2. 在使用 MSSQL 传输的 NServiceBus 队列上发布消息。

如果没有事务范围,这工作正常,但是,当添加事务范围时,我被要求使用以下方式打开对分布式事务的支持:

TransactionManager.ImplicitDistributedTransactions = true;

Worker服务中可执行代码如下:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        int number = 0;
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                number = number + 1;
                using var transactionScope = TransactionUtils.CreateTransactionScope();
              
               
                await SaveDummyDataIntoTable2Dapper($"saved {number}").ConfigureAwait(false);
             
                await messageSession.Publish(new MyMessage { Number = number }, stoppingToken)
                    .ConfigureAwait(false);

                _logger.LogInformation("Publishing message {number}", number);
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                 transactionScope.Complete();
                _logger.LogInformation("Transaction complete");
                await Task.Delay(1000, stoppingToken);
            }
        }
        catch (Exception e)
        {
            _logger.LogError("Exception: {ex}", e);
            throw;
        }
    }

交易范围是使用以下参数创建的:

public class TransactionUtils 
{
    public static TransactionScope CreateTransactionScope()
    {
        var transactionOptions = new TransactionOptions();
        transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
        transactionOptions.Timeout = TransactionManager.MaximumTimeout;
        return new TransactionScope(TransactionScopeOption.Required, transactionOptions,TransactionScopeAsyncFlowOption.Enabled);
    }
}

保存到数据库的代码使用简单的简洁的 GenericRepository 库:

private async Task SaveDummyDataIntoTable2Dapper(string data)
    {
        using var scope = ServiceProvider.CreateScope();
        var mainTableRepository = 
            scope.ServiceProvider
                .GetRequiredService<MainTableRepository>();
        await mainTableRepository.InsertAsync(new MainTable()
        {
            Data = data,
            UpdatedDate = DateTime.Now
        });
    }

我必须在这里使用范围,因为存储库是有范围的,并且工作人员是单例的,所以它不能直接注入。

我尝试过使用 EF Core 进行持久化,也得到了相同的结果:

Transaction.Complete() 行通过,然后当尝试处理事务范围时它会挂起(有时它会设法插入几行然后挂起)。

没有事务范围,一切正常

我不确定我在这里遗漏了什么(如果有的话),或者只是这在 .NET7 中仍然不起作用?

请注意,我的机器上启用了 MSDTC,并且我在 Windows 10 上执行此操作

distributed-transactions .net-7.0
4个回答
2
投票

确保您使用的是 Microsoft.Data.SqlClient +v5.1 替换所有“usings”System.Data.SqlClient > Microsoft.Data.SqlClient

确保 ImplicitDistributedTransactions 设置为 True:

TransactionManager.ImplicitDistributedTransactions = true;

using (var ts = new TransactionScope(your options))
{
    TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
    
    ... your code ..
    
    
    ts.Complete();
}

0
投票

我们已经能够使用以下代码解决这个问题。

通过此修改,DTC 实际上可以正确调用并在 .NET7 中工作。

using var transactionScope = TransactionUtils.CreateTransactionScope().EnsureDistributed();

扩展方法EnsureDistributed实现如下:

   public static TransactionScope EnsureDistributed(this TransactionScope ts)
    {
        Transaction.Current?.EnlistDurable(DummyEnlistmentNotification.Id, new DummyEnlistmentNotification(),
            EnlistmentOptions.None);

        return ts;
    }

    internal class DummyEnlistmentNotification : IEnlistmentNotification
    {
        internal static readonly Guid Id = new("8d952615-7f67-4579-94fa-5c36f0c61478");
        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            preparingEnlistment.Prepared();
        }
        public void Commit(Enlistment enlistment)
        {
            enlistment.Done();
        }
        public void Rollback(Enlistment enlistment)
        {
            enlistment.Done();
        }
        public void InDoubt(Enlistment enlistment)
        {
            enlistment.Done();
        }

这是 10 年前的代码片段,但它可以工作(我猜测是因为 .NET Core 只是从 .NET 复制和重构了 DistributedTransactions 的代码,这也复制了错误)。

它的作用是立即创建分布式事务,而不是创建 LTM 事务,然后在需要时将其提升到 DTC。

更多详细说明可以在这里找到:

https://www.davidboike.dev/2010/04/forcously-creating-a-distributed-net-transaction/

https://github.com/davybrion/companysite-dotnet/blob/master/content/blog/2010-03-msdtc-woes-with-nservicebus-and-nhibernate.md


0
投票

对于使用可以分散在多个业务类中的多个嵌套

TransactionScope
实例的任何人,我想出了一个简单的类,它可以让您保持类似的方法,但启用
TransactionManager.ImplicitDistributedTransactions
标志并使用 lambda 将作用域链接到它功能方便使用。

*请注意,这仅经过了轻微测试,但似乎有效!我目前正在 EF Core 7.0.4 上运行。

public class InternalTransactionScope
    {
        /// <summary>
        /// Executes an <see cref="Action"/> within the context
        /// of a <see cref="TransactionScope"/> that has enabled
        /// support for distributed transactions.
        /// </summary>
        /// <param name="action"></param>
        public static void ExecuteTransaction(Action action)
        {
            //enable distributed transactions
            TransactionManager.ImplicitDistributedTransactions = true;

            using (var scope = new TransactionScope())
            {
                //link this scope to our overall transaction
                TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);

                //execute and complete the scope
                action();
                scope.Complete();
            }
        }
        
        /// <summary>
        /// Executes an <see cref="Action"/> within the context
        /// of a <see cref="TransactionScope"/> that has enabled
        /// support for distributed transactions and returns the
        /// result of the execution.
        /// </summary>
        /// <typeparam name="T">The type of return value expected.</typeparam>
        /// <param name="action">The action to execute and retrieve a value from.</param>
        /// <returns>An instance of <typeparamref name="T"/> representing the result of the request.</returns>
        public static T ExecuteTransaction<T>(Func<T> action)
        {
            //enable distributed transactions
            TransactionManager.ImplicitDistributedTransactions = true;

            using (var scope = new TransactionScope())
            {
                //link this scope to our overall transaction
                TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);

                //execute and complete the scope
                var result = action();
                scope.Complete();

                return result;
            }
        }
    }

0
投票

在撰写本文时,事务范围曾经在 .NET Framework 上运行时存在死锁问题。死锁已作为 https://github.com/dotnet/SqlClient/pull/1242 的一部分得到修复,但从未移植到 .NET。随着 Windows 上引入对 .NET 7 及更高版本的分布式事务支持,可能会遇到同样的死锁。我已经提交了一个 pull request 希望能解决这个问题。

© www.soinside.com 2019 - 2024. All rights reserved.