不使用服务定位器,从两个作用域使用作用域服务(如DbContext)
独立DbContext工作单元的无DI框架依赖实现
核心需求回顾
- 执行两个独立工作单元,第一个失败回滚后第二个必须正常执行
- 通过依赖注入提供DbContext,禁止手动创建或使用服务定位器
- 服务尽可能减少对DI框架具体类型的依赖,同时保持依赖关系清晰可查
解决方案:抽象作用域工厂 + 独立工作单元
通过定义自定义抽象接口封装作用域创建逻辑,让业务服务只依赖抽象接口而非DI框架类型,同时将每个工作单元封装为独立服务,明确依赖关系。
1. 定义抽象作用域接口
先定义两个核心抽象:负责创建作用域的工厂,以及作用域本身
// 抽象作用域工厂:用于创建独立工作单元的作用域 public interface IUnitOfWorkScopeFactory { IUnitOfWorkScope CreateScope(); } // 抽象作用域:提供在当前作用域内解析服务的能力 public interface IUnitOfWorkScope : IDisposable { T Resolve<T>() where T : notnull; }
2. 基于DI框架实现抽象接口(以ASP.NET Core为例)
这部分是唯一依赖DI框架的代码,隔离在单独实现类中,业务服务无需感知
// 实现作用域工厂:封装ASP.NET Core的IServiceScopeFactory public class AspNetCoreUnitOfWorkScopeFactory : IUnitOfWorkScopeFactory { private readonly IServiceScopeFactory _serviceScopeFactory; public AspNetCoreUnitOfWorkScopeFactory(IServiceScopeFactory serviceScopeFactory) { _serviceScopeFactory = serviceScopeFactory; } public IUnitOfWorkScope CreateScope() { return new AspNetCoreUnitOfWorkScope(_serviceScopeFactory.CreateScope()); } } // 实现作用域:封装ASP.NET Core的IServiceScope public class AspNetCoreUnitOfWorkScope : IUnitOfWorkScope { private readonly IServiceScope _scope; public AspNetCoreUnitOfWorkScope(IServiceScope scope) { _scope = scope; } public T Resolve<T>() where T : notnull { return _scope.ServiceProvider.GetRequiredService<T>(); } public void Dispose() { _scope.Dispose(); } }
3. 注册服务到DI容器
在Startup/Program.cs中完成服务注册:
// 注册自定义作用域工厂 services.AddScoped<IUnitOfWorkScopeFactory, AspNetCoreUnitOfWorkScopeFactory>(); // 注册DbContext(默认Scoped生命周期,符合独立工作单元需求) services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); // 注册工作单元服务 services.AddScoped<IFirstWorkUnit, FirstWorkUnit>(); services.AddScoped<ISecondWorkUnit, SecondWorkUnit>();
4. 封装独立工作单元
每个工作单元专注于自身业务逻辑,直接注入DbContext,无需关心作用域:
// 第一个工作单元接口 public interface IFirstWorkUnit { Task<bool> ExecuteAsync(); } // 第一个工作单元实现 public class FirstWorkUnit : IFirstWorkUnit { private readonly MyDbContext _dbContext; public FirstWorkUnit(MyDbContext dbContext) { _dbContext = dbContext; } public async Task<bool> ExecuteAsync() { try { // 执行第一个工作单元的业务操作 _dbContext.Orders.Add(new Order { /* 订单数据 */ }); await _dbContext.SaveChangesAsync(); return true; } catch { // 无需手动回滚:作用域Dispose后DbContext会被释放,未提交的变更自动丢弃 return false; } } } // 第二个工作单元接口 public interface ISecondWorkUnit { Task ExecuteAsync(); } // 第二个工作单元实现 public class SecondWorkUnit : ISecondWorkUnit { private readonly MyDbContext _dbContext; public SecondWorkUnit(MyDbContext dbContext) { _dbContext = dbContext; } public async Task ExecuteAsync() { // 执行第二个工作单元的业务操作(不受第一个单元失败影响) _dbContext.OperationLogs.Add(new OperationLog { Message = "第一个工作单元执行完成(成功/失败)", Timestamp = DateTime.UtcNow }); await _dbContext.SaveChangesAsync(); } }
5. 业务服务实现
业务服务仅依赖自定义抽象接口,依赖关系清晰,完全脱离DI框架具体类型:
public class OrderProcessingService { private readonly IUnitOfWorkScopeFactory _scopeFactory; // 服务签名明确体现依赖:需要创建独立工作单元的能力 public OrderProcessingService(IUnitOfWorkScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task ProcessOrderAsync() { bool firstUnitSuccess = false; // 处理第一个工作单元:独立作用域保证DbContext隔离 using (var firstScope = _scopeFactory.CreateScope()) { var firstWorkUnit = firstScope.Resolve<IFirstWorkUnit>(); firstUnitSuccess = await firstWorkUnit.ExecuteAsync(); } // 处理第二个工作单元:无论第一个是否成功,都执行 using (var secondScope = _scopeFactory.CreateScope()) { var secondWorkUnit = secondScope.Resolve<ISecondWorkUnit>(); await secondWorkUnit.ExecuteAsync(); } } }
方案优势
- 依赖关系清晰:服务签名明确展示所需依赖(
IUnitOfWorkScopeFactory),而非模糊的DI框架类型 - 低DI框架耦合:业务服务完全不依赖ASP.NET Core的
IServiceScopeFactory,更换DI框架仅需重新实现抽象接口 - 工作单元隔离:每个工作单元在独立作用域内获取DbContext,第一个单元的变更不会污染第二个单元
- 单一职责原则:工作单元专注于业务逻辑,作用域工厂专注于作用域创建,职责划分清晰
对比不推荐的实现(注入IServiceScopeFactory)
// 不推荐的方案示例 public class BadOrderProcessingService { private readonly IServiceScopeFactory _scopeFactory; // 问题1:依赖DI框架具体类型,耦合度高 // 问题2:服务签名无法体现需要IFirstWorkUnit和ISecondWorkUnit,依赖不透明 public BadOrderProcessingService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task ProcessOrderAsync() { using (var scope = _scopeFactory.CreateScope()) { var firstWorkUnit = scope.ServiceProvider.GetRequiredService<IFirstWorkUnit>(); await firstWorkUnit.ExecuteAsync(); } using (var scope = _scopeFactory.CreateScope()) { var secondWorkUnit = scope.ServiceProvider.GetRequiredService<ISecondWorkUnit>(); await secondWorkUnit.ExecuteAsync(); } } }
内容的提问来源于stack exchange,提问作者Alpedar
相关产品推荐
相关产品推荐

