c# - 管理事务

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

如何正确实现跨多个存储库的事务?

假设我们有 2 个存储库(临时)

internal sealed class UserRepository : IUserRepository
{
    public UserRepository(IDbConnection connection) {}

    public Task<int> Create(int addressId, string name) {}
}

internal sealed class AddressRepository : IAddressRepository
{

    public AddressRepository(IDbConnection connection, IDbTransaction transaction, ILogger<AddressRepository> logger){}
    public Task<int> Create(string country){}
}

我想在单笔交易中同时调用

IUserRepository.Create
IAddressRepository.Create

到目前为止我尝试过的:

  1. 从存储库获取交易
internal sealed class UserRepository : IUserRepository
{
   public void OpenConnection(){}
   public void CloseConnection(){}
   public IDbTransaction BeginTransaction(){}

   public async Task<int> Create(int addressId, string name)
   {
       OpenConnection();
       using var transaction = BeginTransaction();
       var result = await Create(addressId, name, transaction);
       transaction.Commit();
       CloseConnection();
       return result;
   }
   public Task<int> Create(int addressId, string name, IDbTransaction transaction)
   {
       ArgumentNullException.ThrowIfNull(transaction.Connection);
       return transaction.Connection.ExecuteScalarAsync<int>(command, params, transaction);
   }
}

然而,这对我来说看起来并不干净,每个方法都需要事务重载。

  1. 存储库作为静态类 ...坏主意,不可测试,无法注入其他依赖项,例如

    ILogger<T>

  2. 存储库和交易工厂

这需要对存储库进行一些小调整

internal sealed class UserRepository : IUserRepository
{
    public UserRepository(IDbConnection connection, IDbTransaction transaction){}
    public Task<int> Create(int addressId, string name){}
}

以及管理事务和存储库创建的附加类

internal sealed class RepositoryFactory
{

    public RepositoryFactory(Func<IDbConnection, IDbTransaction, IAddressRepository> addressFactory,
        Func<IDbConnection, IDbTransaction, IUserRepository> userFactory){}

    public IAddressRepository CreateAddressRepository(IDbConnection connection, IDbTransaction transaction){}

    public IUserRepository CreateUserRepository(IDbConnection connection, IDbTransaction transaction){}
}


internal sealed class Transaction : ITransaction
{
    private readonly IDbConnection _connection;
    private readonly RepositoryFactory _repositoryFactory;
    private readonly IDbTransaction _transaction;

    public Transaction(IDbConnection connection, RepositoryFactory repositoryFactory){}

    public IAddressRepository CreateAddressRepository()
    {
        return _repositoryFactory.CreateAddressRepository(_connection, _transaction);
    }

    public IUserRepository CreateUserRepository()
    {
        return _repositoryFactory.CreateUserRepository(_connection, _transaction);
    }


    public void Commit(){}
    public void Rollback(){}
    public void Dispose(){}
}


internal sealed class TransactionFactory : ITransactionFactory
{
    private readonly Func<ITransaction> _transaction;

    public TransactionFactory(Func<ITransaction> transaction){}
    public ITransaction Create()
    {
        return _transaction();
    }
}

使用示例

internal sealed class HighLevelService : IHighLevelService
{
    private readonly ITransactionFactory _transactionFactory;

    public HighLevelService(ITransactionFactory transactionFactory)
    {
        _transactionFactory = transactionFactory;
    }

    public async Task CreateUser(string country, string name)
    {
        using var transaction = _transactionFactory.Create();

        var ar = transaction.CreateAddressRepository();
        var ur = transaction.CreateUserRepository();

        var addressId = await ar.Create(country);
        var userId = await ur.Create(addressId, name);

        transaction.Commit();
    }
}

这很好......几乎。我在每次调用

ITransactionFactory.Create
时都会收到新的交易,并且在幕后的存储库具有相同的连接/交易。我不再需要担心打开和关闭连接了。

但是我认为它仍然有缺陷。

ITransaction
不应该负责创建存储库,这是
RepositoryFactory
的工作。如果我能以某种方式反转依赖性,那就太好了。

internal sealed class RepositoryFactory
{

    public RepositoryFactory(Func<IDbConnection, IDbTransaction, IAddressRepository> addressFactory,
        Func<IDbConnection, IDbTransaction, IUserRepository> userFactory){}

    public IAddressRepository CreateAddressRepository(ITransaction transaction){}

    public IUserRepository CreateUserRepository(ITransaction transaction){}
}

但这需要

ITransaction
看起来像这样。

internal interface ITransaction : IDisposable
{

    IDbConnection Connection {get;}
    IDbTransaction Transaction {get;}
    void Commit();
    void Rollback();
}

我不喜欢。

有没有办法在不暴露底层连接和事务的情况下修复它。或者也许我可以使用其他模式。

完整的依赖注入

builder.Services.AddSingleton<RepositoryFactory>(sp =>
{
    return new RepositoryFactory(
        (connection, transaction) =>
            new AddressRepository(connection, transaction, sp.GetRequiredService<ILogger<AddressRepository>>()),
        (connection, transaction) => new UserRepository(connection, transaction));
});
builder.Services.AddTransient<ITransaction, Transaction>();
builder.Services.AddSingleton<ITransactionFactory>(sp =>
{
    var connection = sp.GetRequiredService<IDbConnection>();
    var repositoryFactory = sp.GetRequiredService<RepositoryFactory>();
    return new TransactionFactory(() => new Transaction(connection, repositoryFactory));
});
builder.Services.AddTransient<IHighLevelService, HighLevelService>();
c# design-patterns dependency-injection ado.net
1个回答
0
投票

这看起来非常复杂。为什么?

存储库需要一个开放的数据库连接才能工作。他们没有责任打开它、关闭它或在上面创建交易。

您的流程应如下所示:

  • 开始
  • 打开数据库连接
  • 开始交易
  • 通过打开的连接创建用户存储库
  • 通过打开的连接创建地址存储库
  • 调用用户存储库方法
  • 调用地址存储方法
  • 提交交易
  • 关闭数据库连接

如何构建它取决于您和您的程序。您可能正在使用依赖项注入来创建新存储库并注入数据库连接。您可能有一个中间件,可以打开事务并在成功时提交它并在异常时回滚它。或者它只是程序流程的一部分。其他任何事情都太复杂了,在一个连接和事务上拥有两个存储库并不是火箭科学,保持简单。

using var connection = OpenDatabase();

using var transaction = connection.BeginTransaction();

var userRepository = new UserRepository(connection);
var addressRepository = new AddressRepository(connection);

var address = addressRepository.Create(data, data, data);
var user = userRepository.Create(data, data, data, address);

transaction.Commit();
connection.Close();

使用依赖注入,代码可能如下所示:

call(IDbConnection connection, IUserRepository userRepository, IAddressRepository addressRepository)
{
    using var transaction = connection.BeginTransaction();

    var address = addressRepository.Create(data, data, data);
    var user = userRepository.Create(data, data, data, address);

    transaction.Commit();
}
© www.soinside.com 2019 - 2024. All rights reserved.