拆分CRUD Repository时的依赖注入(DI)问题咨询
关于拆分CRUD仓储接口的DI与设计疑问解答
首先得说,把ICrudRepository拆分为单一职责的接口(ICreatableRepository、IReadableRepository等)这个思路非常棒——完全符合单一职责原则,能让你按需实现接口,避免被迫实现用不到的方法,这在很多场景下都能大幅简化代码。接下来针对你的两个疑问逐一解答:
1. 依赖注入时,是否需要按模型注入四个独立仓储的实例?
答案是需要,但可以通过开放泛型注册简化操作:
- 如果你的每个模型都有专属的仓储实现(比如
ProductCreatableRepository、OrderCreatableRepository),那确实需要为每个模型的每个接口单独注册,比如在ASP.NET Core的DI容器中:services.AddScoped<ICreatableRepository<Product>, ProductCreatableRepository>(); services.AddScoped<IReadableRepository<Product, Guid>, ProductReadableRepository>(); // 同理注册Updatable、Deletable的Product实现 services.AddScoped<ICreatableRepository<Order>, OrderCreatableRepository>(); // ... 其他模型的注册 - 但如果你的大部分模型都使用通用实现(比如基于EF Core的通用仓储),那可以用开放泛型注册,一次注册就能覆盖所有模型:
这样DI容器会自动根据// 注册通用创建仓储 services.AddScoped(typeof(ICreatableRepository<>), typeof(GenericCreatableRepository<>)); // 注册通用读取仓储 services.AddScoped(typeof(IReadableRepository<,>), typeof(GenericReadableRepository<,>)); // 同理注册Updatable、Deletable的通用实现BaseCrudRepository<TModel, TId>的泛型参数,匹配对应的通用仓储实例,无需逐个模型注册。
2. 在基类构造函数中创建这些实例而非注入,是否属于反模式?
绝对是反模式,原因有三:
- 破坏了依赖倒置原则:基类直接依赖具体实现,而非抽象,一旦需要替换某个操作的实现(比如把Create操作换成批量创建),你必须修改基类代码,违反了开闭原则。
- 无法进行单元测试:你没法Mock这些内部创建的仓储实例,导致基类的单元测试只能依赖真实的数据库或其他外部资源,测试成本极高。
- 失去了DI容器的生命周期管理:如果这些仓储实例需要特定的生命周期(比如Scoped),手动创建会导致生命周期失控,可能引发资源泄漏或状态不一致的问题。
所以一定要坚持通过构造注入的方式传入这些依赖,让DI容器负责实例的创建和管理。
额外建议
如果你的四个独立仓储的通用实现逻辑高度耦合(比如都依赖同一个DbContext),也可以考虑让BaseCrudRepository直接继承这些通用实现,而非通过构造注入依赖它们,比如:
public abstract class BaseCrudRepository<TModel, TId> : GenericCreatableRepository<TModel>, GenericReadableRepository<TModel, TId>, GenericUpdatableRepository<TModel>, GenericDeletableRepository<TId>, IMyNewCrudRepository<TModel, TId> { protected BaseCrudRepository(DbContext dbContext) : base(dbContext) { // 这里复用各通用仓储的构造逻辑 } }
这种方式同样能实现单一职责的接口拆分,同时减少构造注入的依赖数量,不过灵活性会略低于依赖注入四个接口的方式——具体选哪种,取决于你是否需要为单个操作灵活替换实现。
内容的提问来源于stack exchange,提问作者Jack Pettinger
相关产品推荐
相关产品推荐

