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

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











