gpt4 book ai didi

没有 TransactionScope 的 .Net Core 事务装饰器

转载 作者:行者123 更新时间:2023-12-04 19:30:11 25 4
gpt4 key购买 nike

在我的 .NET Core 应用程序中,我有一个装饰器类,我希望它能够通过将数据库命令的执行包装在 TransactionScope 中来处理事务。不幸的是,似乎对 TransactionScope 的支持不会在 .NET Core 2 发布时进入 SqlConnection:https://github.com/dotnet/corefx/issues/19708 :

在没有 TransactionScope 的情况下,我不确定解决这个问题的最佳方法。使用 TransactionScope,我的事务装饰器如下所示:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decorated;

//constructor

public void Handle(TCommand command)
{
using (var scope = new TransactionScope())
{
this.decorated.Handle(command);

scope.Complete();
}
}
}

目前,ICommandHandler 的每个实现都会获取我的 DapperContext 类的实例并处理如下命令:
public void Handle(UpdateEntity command)
{
var sql = Resources.UpdateEntityPart1;

this.context.Execute(sql, new
{
id = command.Id;
});

var sql = Resources.UpdateEntityPart2;

//call Execute again
}

DapperContext 类有一个连接工厂,为每次调用其 Execute 方法提供新的连接。因为命令处理程序可能必须为单个 TCommand 执行多个数据库写入,所以我需要能够在出现故障时回滚。必须在创建连接的同时(在 DapperContext 中)创建事务意味着我无法保证跨连接的事务行为。

我考虑过的一种选择似乎并不那么令人满意:
  • 在命令处理程序级别管理连接和事务,然后将该信息传递给 dapper 上下文。这样,给定命令的所有查询都使用相同的连接和事务。这可能行得通,但我不喜欢让我的命令处理程序承担这个责任的想法。就整体设计而言,DapperContext 成为担心连接的地方似乎更自然。

  • 那么我的问题是:考虑到 .NET Core 中 SqlConnection 的当前限制,有没有办法在不使用 TransactionScope 的情况下编写事务装饰器?如果不是,那么不严重违反单一职责原则的下一个最佳解决方案是什么?

    最佳答案

    一个解决方案可能是创建一个 SqlTransaction作为装饰器的一部分,并将其存储在某种 ThreadLocal 中或 AsyncLocal字段,因此它可用于业务事务的其他部分,即使它没有显式传递。这实际上是 TransactionScope在封面下(但更优雅)。

    例如,看看这个伪代码:

    public class TransactionCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
    {
    private readonly ICommandHandler<TCommand> decorated;
    private readonly AsyncLocal<SqlTransaction> transaction;

    public void Handle(TCommand command)
    {
    transaction.Value = BeginTranscation();

    try
    {
    this.decorated.Handle(command);

    transaction.Value.Commit();
    }
    finally
    {
    transaction.Value.Dispose();
    transaction.Value = null;
    }
    }
    }

    处理程序可以使用的抽象:
    public interface ITransactionContainer
    {
    SqlTransaction CurrentTransaction { get; }
    }


    public void Handle(UpdateEntity command)
    {
    // Get current transaction
    var transaction = this.transactionContainer.CurrentTransaction;

    var sql = Resources.UpdateEntityPart1;

    // Pass the transaction on to the Execute
    // (or hide it inside the execute would be even better)
    this.context.Execute(sql, transaction, new
    {
    id = command.Id;
    });

    var sql = Resources.UpdateEntityPart2;

    //call Execute again
    }
    ITransactionContainer 的实现可能看起来像这样:
    public class AsyncTransactionContainer : ITransactionContainer
    {
    private readonly AsyncLocal<SqlTransaction> transaction;

    public AsyncTransactionContainer(AsyncLocal<SqlTransaction> transaction)
    {
    this.transaction = transaction;
    }

    public SqlTransaction CurrentTransaction =>
    this.transaction.Value
    ?? throw new InvalidOperationException("No transaction.");
    }
    AsyncTransactionContainerTransactionCommandHandlerDecorator取决于 AsyncLocal<SqlTransaction> .这应该是一个单例(应该将相同的实例注入(inject)两者)。

    关于没有 TransactionScope 的 .Net Core 事务装饰器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44163915/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com