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

