Entity Framework中UnitOfWork与Repository模式的正确实现及按需注入问题
我完全懂你的困扰——随着项目里仓储类越来越多,把所有仓储都塞进UnitOfWork的构造器里不仅代码看着乱糟糟,还平白无故实例化一堆用不上的对象,太不划算。下面给你几个我在实际项目里验证过的方案:
1. 用Lazy<T>实现懒加载仓储
这是最直接的解决方案,利用.NET自带的Lazy<T>类型,只有当你真正访问某个仓储的时候,它才会被实例化。结合依赖注入容器(比如Microsoft.Extensions.DependencyInjection、Autofac)就能轻松实现:
首先,你的UnitOfWork可以这么定义:
public class UnitOfWork : IUnitOfWork { private readonly IServiceProvider _serviceProvider; private Lazy<IUserRepository> _userRepository; private Lazy<IOrderRepository> _orderRepository; // 其他仓储的Lazy<T>定义... public UnitOfWork(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; InitLazyRepositories(); } public IUserRepository UserRepository => _userRepository.Value; public IOrderRepository OrderRepository => _orderRepository.Value; // 其他仓储的只读属性... private void InitLazyRepositories() { _userRepository = new Lazy<IUserRepository>(() => _serviceProvider.GetRequiredService<IUserRepository>()); _orderRepository = new Lazy<IOrderRepository>(() => _serviceProvider.GetRequiredService<IOrderRepository>()); // 逐个初始化其他仓储的Lazy实例 } // UnitOfWork核心方法示例 public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { return await _dbContext.SaveChangesAsync(cancellationToken); } }
这个方案的优势很明显:
- 构造器只需要注入
IServiceProvider,不会随着仓储数量增加而臃肿 - 只有当你访问
UserRepository或OrderRepository时,对应的仓储才会被DI容器实例化,避免不必要的资源消耗
注意:如果用的是Microsoft.Extensions.DependencyInjection,要确保仓储注册为**瞬态(Transient)或作用域(Scoped)**生命周期,避免Lazy实例化时机影响生命周期管理。
2. 封装仓储工厂模式
如果觉得直接依赖IServiceProvider不够优雅(有些人认为这是服务定位器模式),可以封装一个仓储工厂,专门负责按需创建仓储实例:
先定义工厂接口:
public interface IRepositoryFactory { TRepository GetRepository<TRepository>() where TRepository : class; }
然后实现这个工厂:
public class RepositoryFactory : IRepositoryFactory { private readonly IServiceProvider _serviceProvider; public RepositoryFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public TRepository GetRepository<TRepository>() where TRepository : class { return _serviceProvider.GetRequiredService<TRepository>(); } }
最后在UnitOfWork里注入工厂,按需获取仓储:
public class UnitOfWork : IUnitOfWork { private readonly IRepositoryFactory _repoFactory; private IUserRepository _userRepository; private IOrderRepository _orderRepository; public UnitOfWork(IRepositoryFactory repoFactory) { _repoFactory = repoFactory; } public IUserRepository UserRepository { get { _userRepository ??= _repoFactory.GetRepository<IUserRepository>(); return _userRepository; } } public IOrderRepository OrderRepository { get { _orderRepository ??= _repoFactory.GetRepository<IOrderRepository>(); return _orderRepository; } } // SaveChanges等核心方法... }
这种方式比直接用IServiceProvider多了一层封装,更符合单一职责原则,也更容易做单元测试(可以Mock工厂)。
3. 动态通用仓储(进阶方案)
如果你的项目已经实现了通用仓储接口(比如IRepository<TEntity>),可以更进一步,让UnitOfWork支持动态获取任意实体的仓储,完全不需要提前定义每个仓储的属性:
public class UnitOfWork : IUnitOfWork { private readonly IServiceProvider _serviceProvider; private readonly Dictionary<Type, object> _cachedRepos = new(); public UnitOfWork(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class { var entityType = typeof(TEntity); if (_cachedRepos.TryGetValue(entityType, out var repo)) { return (IRepository<TEntity>)repo; } var newRepo = _serviceProvider.GetRequiredService<IRepository<TEntity>>(); _cachedRepos[entityType] = newRepo; return newRepo; } // SaveChanges核心方法... }
使用的时候直接这样调用:
var userRepo = unitOfWork.GetRepository<User>(); var orderRepo = unitOfWork.GetRepository<Order>();
这种方案最灵活,不管新增多少实体,都不需要修改UnitOfWork的代码,完美解决构造器臃肿的问题。不过前提是你已经实现了通用仓储模式,这在EF项目里是很常见的做法。
最后想说,虽然确实有不少人质疑EF里用UnitOfWork+Repository的必要性(毕竟EF本身的DbContext就已经是UnitOfWork,DbSet就是Repository),但如果你的项目需要抽象数据访问层、方便切换数据源或者统一业务逻辑中的事务管理,这个模式还是很实用的。你选的方向没问题,解决好按需注入的问题就可以顺畅使用了。
内容的提问来源于stack exchange,提问作者imagran

