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

泛型DbContext仓储基类实现遇依赖注入错误及方案咨询

问题描述

项目中有多个对应不同实体类、业务场景的DbContext,为方便编写单元测试,希望创建可注入泛型DbContext<T>的仓储基类。现有代码及遇到的问题如下:

仓储基类代码

public interface IDbContextRepository<T>
{
    public T Context { get; }
}

public class DbContextRepository<T> : IDbContextRepository<T>
{
    private readonly T _context;

    public DbContextRepository(T context)
    {
        _context = context;
    }

    public T Context { get { return _context; } }
}

仓储实现代码

public interface IMyRepository : IDbContextRepository<AreaDatabaseContext>
{
    IList<Item> GetItems();
}

public class MyRepository : DbContextRepository<AreaDatabaseContext>, IMyRepository
{
    public AccorderenRepository(AreaDatabaseContext context) : base(context) { }

    public IList<Item> GetItems()
    {
        using(var db = new Context())
        {
            //database operations
        }
    }
}

服务注册代码

services.AddSingleton<IMyRepository, MyRepository>();
services.AddSingleton<IDbContextRepository<AreaDatabaseContext>, DbContextRepository<AreaDatabaseContext>>();

启动应用时触发错误:Unable to resolve service for type 'AreaDatabaseContext',同时咨询:当前实现路径是否正确?是否会因DbContext管理问题引发后续麻烦?


问题解决与优化建议

1. 先解决依赖注入报错问题

报错核心原因是未在DI容器中注册AreaDatabaseContext,DI框架无法找到该类型的实例注入到MyRepository中,同时代码存在几处细节错误:

  • 注册DbContext:如果使用EF Core,需通过AddDbContext完成注册(替换为你的数据库连接配置):
    services.AddDbContext<AreaDatabaseContext>(options => 
        options.UseSqlServer(Configuration.GetConnectionString("AreaDbConnection")));
    
  • 修正构造函数名:MyRepository的构造函数名写错为AccorderenRepository,需改为类名一致:
    public MyRepository(AreaDatabaseContext context) : base(context) { }
    
  • 移除冗余的new Context():既然已经通过依赖注入获取了Context,就不需要在方法内重新实例化,直接使用基类注入的实例:
    public IList<Item> GetItems()
    {
        return Context.Items.ToList();
    }
    

2. 实现路径合理性与风险规避

你的核心思路是正确的,但需调整细节避免DbContext管理问题:

(1)严格控制DbContext生命周期

绝对不要用Singleton注册DbContext或依赖它的仓储!DbContext本身不是线程安全的,默认Scoped生命周期(与请求同周期)才是正确选择,因此仓储注册需改为:

services.AddScoped<IMyRepository, MyRepository>();
services.AddScoped<IDbContextRepository<AreaDatabaseContext>, DbContextRepository<AreaDatabaseContext>>();

AddDbContext默认就是Scoped,与仓储生命周期保持一致,可避免多线程冲突、上下文状态混乱等问题。

(2)优化仓储基类设计

当前基类仅简单暴露Context,可扩展通用CRUD方法提升复用性,减少重复代码:

public class DbContextRepository<TContext> : IDbContextRepository<TContext> 
    where TContext : DbContext
{
    protected readonly TContext _context;

    public DbContextRepository(TContext context)
    {
        _context = context;
    }

    public TContext Context => _context;

    // 通用新增方法
    public void Add<TEntity>(TEntity entity) where TEntity : class
    {
        _context.Set<TEntity>().Add(entity);
    }

    // 通用按ID查询方法
    public async Task<TEntity> GetById<TEntity>(int id) where TEntity : class
    {
        return await _context.Set<TEntity>().FindAsync(id);
    }
}

后续业务仓储只需实现特殊业务逻辑即可。

(3)单元测试便利性验证

你的设计确实能大幅降低单元测试难度,可通过Mock框架(如Moq)轻松模拟DbContext注入:

// 单元测试示例
var mockContext = new Mock<AreaDatabaseContext>();
var mockDbSet = new Mock<DbSet<Item>>();
// 配置Mock返回预期数据
mockContext.Setup(c => c.Items).Returns(mockDbSet.Object);

var repository = new MyRepository(mockContext.Object);
var result = repository.GetItems();
// 断言结果正确性

3. 总结

  • 先完成DbContext注册,修正代码中的构造函数、实例化错误;
  • 统一使用Scoped生命周期管理DbContext与仓储,规避线程安全风险;
  • 扩展基类通用方法可提升代码复用性;
  • 整体实现路径合理,能很好地支持单元测试,只要严格遵循DbContext生命周期规范,不会引发额外麻烦。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 06:52:53