You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何正确实现跨多个Repository的事务?

在C#中实现跨多个Repository的事务方案探讨

假设我们有两个瞬时(transient)的Repository,需要在同一个事务中调用IUserRepository.Create和IAddressRepository.Create方法:

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){}
}

已尝试的方案

方案1:从Repository内部管理事务

为Repository添加事务相关方法及重载:

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);
   }
}

缺点:代码冗余,每个Repository方法都需要添加事务重载,整体不够简洁。

方案2:将Repository设为静态类

直接定义静态Repository,但这是糟糕的设计:

  • 无法进行单元测试
  • 无法注入ILogger<T>等依赖项

方案3:事务工厂+Repository工厂

调整后的Repository

为Repository注入连接和事务:

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

事务与Repository管理类

需要额外类封装事务和Repository的创建逻辑:

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();
    }
}

使用示例

在高层服务中通过事务工厂获取事务,再创建Repository执行操作:

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都能获取独立事务,底层Repository共享同一连接/事务,无需手动管理连接开闭。
待优化点:ITransaction承担了Repository创建职责,违反单一职责原则。如果要实现依赖反转,让RepositoryFactory负责创建Repository,就需要ITransaction暴露底层的IDbConnection和IDbTransaction,但这会暴露底层细节:

// 调整后的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();
}

核心问题:有没有办法在不暴露底层连接和事务的前提下,解决ITransaction与RepositoryFactory职责混淆的问题?或者有其他更合适的模式?


完整依赖注入配置

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>();

内容的提问来源于stack exchange,提问作者Azarah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 21:59:58