直接 new SqlRepository() 错在破坏解耦,使业务代码依赖具体实现;正确做法是通过 DI 容器按作用域注入 IRepository<T>,并避免暴露 ORM 细节、滥用 DbContext 生命周期及手动注册泛型仓储。

为什么直接 new SqlRepository() 是错的
仓储模式的核心是解耦,不是换个名字写 DAL。如果业务代码里直接 new SqlRepository<User>(),等于把具体实现钉死在 SQL Server 上,接口形同虚设,后续想切到内存仓储或 MongoDB 就得改满屏 new。
正确做法是只依赖 IRepository<T> 接口,靠 DI 容器注入具体实现:
services.AddScoped(typeof(IRepository<>), typeof(SqlRepository<>));
这样替换仓储只需改注册行,不碰业务逻辑。漏掉泛型注册、或写成 services.AddScoped<IRepository<User>, SqlRepository<User>>()(非泛型注册),会导致每种实体都要手动注册,维护成本爆炸。
IRepository 该定义哪些方法才不翻车
别照搬 EF 的 DbSet 全抄——AsNoTracking()、Include() 这些属于 ORM 细节,暴露出去等于把 EF 泄露到应用层。仓储接口要聚焦「领域意图」,不是「数据库操作」。
-
GetByIdAsync(id):必须,主键查询是高频场景 -
FindAsync(Expression<Func<T, bool>> predicate):支持简单条件,但别塞复杂 Join 或分页参数 -
AddAsync(T entity)和UpdateAsync(T entity):实体状态由仓储内部管理,业务层不操心Attach/Modified - 坚决不加
Delete(T entity)—— 删除应走领域服务或软删标记,避免误删
分页、排序、多表关联这些,交给专门的 IQueryService<T> 或 CQRS 查询对象,别塞进 IRepository。
SqlRepository 怎么避免 EF Core 的 DbContext 生命周期陷阱
最常见的坑是把 DbContext 当单例用:services.AddSingleton<DbContext, AppDbContext>()。EF Core 的 DbContext 不是线程安全的,且自带变更跟踪,单例会导致并发修改异常或脏数据。
必须按作用域生命周期注册:
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString),
ServiceLifetime.Scoped);
然后在 SqlRepository<T> 构造函数里接收 AppDbContext,让它自动随请求创建/释放。别在仓储里自己 new DbContext(),也别试图缓存 DbSet<T> 实例——DbContext 被释放后,它的 DbSet 就失效了。
测试时怎么快速换掉 SqlRepository
单元测试不能连真实数据库。最轻量的做法是用内存集合模拟仓储行为,而不是上 Moq 去 mock 接口——mock 容易写出“假通过”的测试(比如忘了验证 SaveChanges 是否被调)。
写一个 InMemoryRepository<T>:
public class InMemoryRepository<T> : IRepository<T> where T : class
{
private readonly List<T> _items = new();
public Task<T> GetByIdAsync(object id) => Task.FromResult(_items.FirstOrDefault(x => GetId(x).Equals(id)));
public Task AddAsync(T entity) { _items.Add(entity); return Task.CompletedTask; }
// ... 其他方法直接操作 _items
}
测试时注册它:services.AddScoped<IRepository<User>, InMemoryRepository<User>>()。注意:这个类只用于测试,别放进生产代码;真要隔离 DB,集成测试阶段再用 Testcontainers 启个临时 SQL Server。
仓储模式真正难的不是写几个接口,而是守住边界——不让数据访问细节渗入应用服务,也不让领域规则散落到 SQL 语句里。一旦开始在仓储方法里拼接动态 SQL 或处理事务边界,就说明接口职责已经模糊了。


















