Blazor Server中如何结合DbContextFactory与仓储模式使用?
问题描述
我有一个Blazor Server端应用,已配置DbContextFactory:
services.AddDbContextFactory<AppContext>(options => { options.UseSqlServer(configuration.GetConnectionString("App")); });同时我使用仓储模式,例如当前的CustomerRepository实现如下:
public class CustomerRepository : Repository<Customer>, ICustomerRepository { public CustomerRepository(AppContext context) : base(context) { } }我还有一些通过依赖注入获取多个仓储的服务,这些仓储需要处于同一个“工作单元”中。请问如何配置仓储以获取同一个DbContext实例?能否结合DbContextFactory使用仓储模式,还是需要通过new创建仓储?
解决方案:结合 DbContextFactory 实现工作单元下的仓储共享 DbContext
不需要手动new仓储,通过**工作单元(Unit of Work)**模式配合DbContextFactory,就能让多个仓储共用同一个DbContext实例,同时保留依赖注入的可维护性和可测试性。
1. 实现工作单元类
工作单元负责从Factory创建共享的DbContext,并提供各个仓储实例,确保所有仓储使用同一上下文:
public class UnitOfWork : IUnitOfWork { private readonly IDbContextFactory<AppContext> _contextFactory; private AppContext _context; private ICustomerRepository _customerRepository; // 按需添加其他仓储接口 public UnitOfWork(IDbContextFactory<AppContext> contextFactory) { _contextFactory = contextFactory; } // 延迟初始化共享DbContext private AppContext Context => _context ??= _contextFactory.CreateDbContext(); // 提供仓储实例,确保共用同一个Context public ICustomerRepository CustomerRepository => _customerRepository ??= new CustomerRepository(Context); // 统一提交事务并释放资源 public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { try { return await Context.SaveChangesAsync(cancellationToken); } finally { await DisposeAsync(); } } // 实现异步释放 public async ValueTask DisposeAsync() { if (_context != null) { await _context.DisposeAsync(); _context = null; } } } // 定义工作单元接口 public interface IUnitOfWork : IAsyncDisposable { ICustomerRepository CustomerRepository { get; } Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); }
2. 注册工作单元到DI容器
在Program.cs中注册工作单元,推荐使用Scoped生命周期(Blazor Server中Scoped对应每个SignalR连接,适配工作单元的单次业务流程生命周期):
services.AddScoped<IUnitOfWork, UnitOfWork>(); // 仓储接口无需单独注册,由工作单元负责创建实例
3. 在业务服务中使用工作单元
业务服务通过DI获取IUnitOfWork,从中获取的多个仓储会自动共享同一个DbContext:
public class CustomerService { private readonly IUnitOfWork _unitOfWork; public CustomerService(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task TransferCustomerDataAsync(int sourceId, int targetId) { // 从工作单元获取多个仓储,共用同一DbContext var customerRepo = _unitOfWork.CustomerRepository; var orderRepo = _unitOfWork.OrderRepository; // 假设存在该仓储 // 执行业务逻辑 var sourceCustomer = await customerRepo.GetByIdAsync(sourceId); var targetCustomer = await customerRepo.GetByIdAsync(targetId); // ... 业务操作 // 统一提交所有变更 await _unitOfWork.SaveChangesAsync(); } }
为什么不手动new仓储?
手动new仓储会脱离DI容器的管理,无法统一控制DbContext的生命周期,也难以对仓储进行Mock测试。通过工作单元模式,既能利用DbContextFactory的多实例并行能力,又能保证同一业务流程内的仓储共享上下文,同时保留DI的优势。
替代方案:直接注入Scoped DbContext(不推荐)
如果你的应用不需要并行操作DbContext,也可以将DbContext注册为Scoped(替代DbContextFactory):
services.AddDbContext<AppContext>(options => options.UseSqlServer(configuration.GetConnectionString("App")));
然后注册仓储为Scoped:
services.AddScoped<ICustomerRepository, CustomerRepository>();
同一Scoped生命周期内的多个仓储会自动获取同一个DbContext实例,但这种方式不支持并行操作DbContext,灵活性不如DbContextFactory+工作单元的方案。
内容的提问来源于stack exchange,提问作者Ivan Debono

