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
相关产品推荐
相关产品推荐

