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

Blazor Server中如何结合DbContextFactory与仓储模式使用?

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:40:09