仓储模式非必需抽象层,仅在需替换数据源、提升可测性或支持多存储类型时才值得引入;否则直接使用dbcontext的dbset更轻量可控。

仓储模式不是必须加的抽象层,当你需要替换数据源、做可测业务逻辑、或应对多存储类型(SQL/内存/NoSQL)时才真正值得引入;否则直接用 DbContext 的 DbSet<t></t> 更轻量、更可控。
什么时候该定义 IRepository<t></t> 接口
泛型接口容易沦为 CRUD 装饰壳,掩盖真实业务语义。只有满足以下任一条件,才建议抽象:
- 你明确要在单元测试中用
InMemoryRepository或Mock<irepository>></irepository>替换数据库访问 - 不同聚合根的数据访问行为差异大(比如
Order需事务+事件发布,Product允许最终一致性) - 你正在对接非 EF Core 的持久化方案(Dapper、MongoDB Driver、EventStoreDB)
- 服务层频繁出现硬编码查询,如
_context.Orders.Where(...).Include(...).ToList(),且跨多个方法重复
为什么 IOrderRepository 比 IRepository<order></order> 更合理
真实订单操作极少是纯增删改查。状态校验、库存预占、履约超时处理、事件发布等逻辑无法塞进泛型接口,强行放进去会破坏单一职责,也模糊领域意图。
具体仓储接口应反映业务动词:
Task<order> GetActiveOrderByCustomerIdAsync(Guid customerId)</order>Task<bool> TryReserveInventoryAsync(Order order)</bool>Task MarkAsShippedAsync(Guid orderId, string trackingNumber)
这类方法天然不适合泛型接口,也避免把 IQueryable<t></t> 泄露到服务层——它会让上层意外触发 N+1 查询或未优化的 SQL。
EF Core 下实现仓储最容易漏掉的三件事
很多示例只写了 SaveChangesAsync() 就收工,但实际部署时这三点常导致隐性故障:
-
DbContext生命周期必须是Scoped,注册时写services.AddDbContext<appdbcontext>(ServiceLifetime.Scoped)</appdbcontext>;设成Singleton会导致并发写入异常 - 仓储构造函数里别
new DbContext(),必须通过 DI 获取实例;否则和UnitOfWork不共享上下文,事务会失效 - 别在仓储里手动调
BeginTransaction();EF Core 的UseTransaction()或更高层的ITransactionScopeFactory才能安全支持嵌套调用,否则易抛InvalidOperationException: A second operation started on this context before a previous operation completed
最常被忽略的是:仓储不等于“所有数据访问都要过它”。简单列表页、只读看板、临时导出脚本,绕过仓储直连 DbSet 反而更清晰。模式的价值在于解决具体问题,而不是填满架构图里的每个方块。











