C# .NET Core 如何在UnitOfWork中实现类似EF的Set<T>泛型方法按类型访问仓储
UnitOfWork 泛型Set仓储获取方法实现
核心实现思路:内部维护「仓储接口类型 - 仓储实例」的映射缓存,通过泛型方法匹配返回对应仓储实例,和EF Core的Set<T>调用体验对齐,同时完全兼容原有硬编码仓储属性的旧代码,不需要改动现有控制器的业务逻辑。
1. 扩展IUnitOfWork接口
在原有接口基础上新增泛型仓储获取方法声明,保留原有具体仓储属性的定义,完全兼容历史代码的调用:
public interface IUnitOfWork : IDisposable { // 原有具体仓储属性,适配历史调用 IBooksRepository Books { get; } ICatalogueRepository Catalogues { get; } // 新增泛型仓储获取方法,可根据业务需要追加更严格的泛型约束 TRepository Set<TRepository>() where TRepository : class; }
2. UnitOfWork核心实现
类内部维护仓储实例缓存字典,结合DI容器完成实例解析,无硬编码的仓储依赖:
public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _dbContext; private readonly IServiceProvider _serviceProvider; // 仓储实例缓存字典,键为仓储接口类型,值为对应实例 private readonly Dictionary<Type, object> _repositoryCache = new(); public UnitOfWork(AppDbContext dbContext, IServiceProvider serviceProvider) { _dbContext = dbContext; _serviceProvider = serviceProvider; } // 原有仓储属性直接复用Set方法返回实例,无需重复初始化 public IBooksRepository Books => Set<IBooksRepository>(); public ICatalogueRepository Catalogues => Set<ICatalogueRepository>(); // 核心泛型Set方法实现 public TRepository Set<TRepository>() where TRepository : class { var repositoryInterfaceType = typeof(TRepository); // 优先从缓存匹配当前请求周期内已初始化的仓储实例 if (_repositoryCache.TryGetValue(repositoryInterfaceType, out var cachedInstance)) { return (TRepository)cachedInstance; } // 缓存未命中时,从DI容器解析对应仓储实例 var resolvedInstance = _serviceProvider.GetRequiredService<TRepository>(); // 实例写入缓存,供后续同请求周期内复用 _repositoryCache[repositoryInterfaceType] = resolvedInstance; return resolvedInstance; } // 原有工作单元逻辑完全保留 public int Complete() => _dbContext.SaveChanges(); public void Dispose() => _dbContext.Dispose(); }
3. DI配置适配
- 所有仓储接口与实现类的原有DI注册逻辑完全不需要改动,Set方法会自动从容器解析对应实例。
- UnitOfWork保持原有Scoped生命周期注册即可,与DbContext、仓储的生命周期保持一致,缓存的仓储实例为请求级别,无跨实例泄漏风险。
4. 调用兼容效果
- 原有硬编码调用方式不受影响,可正常运行:
// 旧代码无侵入改造 var allBooks = _unitOfWork.Books.GetAll();
- 支持泛型通用逻辑调用,可编写通用查询、权限校验逻辑,无需为每个仓储重复写相似代码:
// 新的泛型调用方式,对齐EF Core Set<T>体验 public async Task<List<TEntity>> GetAllAsync<TRepository, TEntity>() where TRepository : IBaseRepository<TEntity> { var repository = _unitOfWork.Set<TRepository>(); return await repository.GetAllAsync(); }
可选优化点
- 若所有仓储接口都继承统一的
IRepository基接口,可将Set方法的泛型约束调整为where TRepository : IRepository,在编译期拦截非仓储类型的错误传入,提升类型安全性。 - 若项目未使用DI托管仓储实例,可在类初始化阶段手动将接口与实例的映射写入缓存字典,无需改动Set方法的核心逻辑。
内容的提问来源于stack exchange,提问作者Rahul
相关产品推荐
相关产品推荐

