电商项目Unit of Work模式实现:通用仓储如何遵循接口隔离原则?
问题:电商项目中Unit of Work模式下通用仓储的设计矛盾
我在电商店铺项目里要实现Unit of Work模式,但卡在了通用仓储的通用性与接口隔离原则(Interface Segregation Principle)的平衡上。比如新增的模型不需要Update方法,但当前的通用仓储会强制让所有模型都继承包含Update的接口,这就违反了ISP。
我知道可以通过服务层调用仓储操作,再用Unit of Work统一保存更改,但如果不用通用仓储又会违反开闭原则(Open Closed Principle)。
现有通用仓储代码
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { protected readonly ApplicationDbContext _context; private readonly DbSet<TEntity> _dbSet; public Repository(ApplicationDbContext context) { _context = context; _dbSet = context.Set<TEntity>(); } public async Task<TEntity> GetByIdAsync(int id) { return await _dbSet.FindAsync(id); } public async Task<IEnumerable<TEntity>> GetAllAsync() { return await _dbSet.ToListAsync(); } public async Task AddAsync(TEntity entity) { await _dbSet.AddAsync(entity); } public void Update(TEntity entity) { _dbSet.Update(entity); } public void Remove(TEntity entity) { _dbSet.Remove(entity); } }
现有Unit of Work代码
public class UnitOfWork : IUnitOfWork { private readonly ApplicationDbContext _context; private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>(); public UnitOfWork(ApplicationDbContext context) { _context = context; } public IRepository<TEntity> Repository<TEntity>() where TEntity : class { if (_repositories.ContainsKey(typeof(TEntity))) return (IRepository<TEntity>)_repositories[typeof(TEntity)]; var repository = new Repository<TEntity>(_context); _repositories.Add(typeof(TEntity), repository); return repository; } public async Task<int> CompleteAsync() { return await _context.SaveChangesAsync(); } public void Dispose() { _context.Dispose(); } }
解决方案:拆分仓储接口,按需组合实现
1. 拆分单一仓储接口为多个职责单一的小接口
把原来的IRepository<T>拆分成专注单一操作的接口,每个接口只包含对应职责的方法,避免强制模型继承不需要的操作:
// 基础查询接口 public interface IReadRepository<TEntity> where TEntity : class { Task<TEntity> GetByIdAsync(int id); Task<IEnumerable<TEntity>> GetAllAsync(); } // 新增接口 public interface ICreateRepository<TEntity> where TEntity : class { Task AddAsync(TEntity entity); } // 更新接口 public interface IUpdateRepository<TEntity> where TEntity : class { void Update(TEntity entity); } // 删除接口 public interface IDeleteRepository<TEntity> where TEntity : class { void Remove(TEntity entity); }
2. 实现基础通用仓储,按需继承接口
创建基础仓储类实现所有拆分后的接口,满足全功能模型的需求;对于仅需部分操作的模型,单独实现对应接口的仓储类:
// 全功能通用仓储,实现所有接口 public class Repository<TEntity> : IReadRepository<TEntity>, ICreateRepository<TEntity>, IUpdateRepository<TEntity>, IDeleteRepository<TEntity> where TEntity : class { protected readonly ApplicationDbContext _context; private readonly DbSet<TEntity> _dbSet; public Repository(ApplicationDbContext context) { _context = context; _dbSet = context.Set<TEntity>(); } public async Task<TEntity> GetByIdAsync(int id) => await _dbSet.FindAsync(id); public async Task<IEnumerable<TEntity>> GetAllAsync() => await _dbSet.ToListAsync(); public async Task AddAsync(TEntity entity) => await _dbSet.AddAsync(entity); public void Update(TEntity entity) => _dbSet.Update(entity); public void Remove(TEntity entity) => _dbSet.Remove(entity); } // 示例:仅支持查询和新增的仓储(比如日志类不可修改的模型) public class ReadCreateRepository<TEntity> : IReadRepository<TEntity>, ICreateRepository<TEntity> where TEntity : class { protected readonly ApplicationDbContext _context; private readonly DbSet<TEntity> _dbSet; public ReadCreateRepository(ApplicationDbContext context) { _context = context; _dbSet = context.Set<TEntity>(); } public async Task<TEntity> GetByIdAsync(int id) => await _dbSet.FindAsync(id); public async Task<IEnumerable<TEntity>> GetAllAsync() => await _dbSet.ToListAsync(); public async Task AddAsync(TEntity entity) => await _dbSet.AddAsync(entity); }
3. 修改Unit of Work,支持按需获取不同类型的仓储
调整Unit of Work的方法,允许根据接口类型获取对应仓储,同时保持实例复用:
public class UnitOfWork : IUnitOfWork { private readonly ApplicationDbContext _context; private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>(); public UnitOfWork(ApplicationDbContext context) { _context = context; } // 通用方法:获取指定接口类型的仓储 public TRepo GetRepository<TRepo, TEntity>() where TRepo : class where TEntity : class { var key = typeof(TRepo).MakeGenericType(typeof(TEntity)); if (_repositories.ContainsKey(key)) return (TRepo)_repositories[key]; // 根据接口类型创建对应仓储实例 TRepo repository; if (typeof(TRepo) == typeof(IReadRepository<TEntity>) || typeof(TRepo) == typeof(ICreateRepository<TEntity>)) { repository = new ReadCreateRepository<TEntity>(_context) as TRepo; } else { // 默认返回全功能仓储 repository = new Repository<TEntity>(_context) as TRepo; } _repositories.Add(key, repository); return repository; } // 简化方法:快速获取全功能仓储 public Repository<TEntity> GetFullRepository<TEntity>() where TEntity : class { return GetRepository<Repository<TEntity>, TEntity>(); } public async Task<int> CompleteAsync() => await _context.SaveChangesAsync(); public void Dispose() => _context.Dispose(); }
4. 服务层按需使用对应仓储
在服务层中,仅注入模型需要的仓储接口,比如对于日志模型,只使用查询和新增接口:
public class LogService { private readonly IReadRepository<Log> _readRepo; private readonly ICreateRepository<Log> _createRepo; private readonly IUnitOfWork _unitOfWork; public LogService(IUnitOfWork unitOfWork) { _readRepo = unitOfWork.GetRepository<IReadRepository<Log>, Log>(); _createRepo = unitOfWork.GetRepository<ICreateRepository<Log>, Log>(); } public async Task AddLogAsync(Log log) { await _createRepo.AddAsync(log); await _unitOfWork.CompleteAsync(); } public async Task<IEnumerable<Log>> GetAllLogsAsync() { return await _readRepo.GetAllAsync(); } }
这种设计既保留了通用仓储的复用性(符合开闭原则),又通过接口拆分避免了强制模型继承不需要的方法(符合接口隔离原则),同时Unit of Work依然可以统一管理事务和保存操作。
内容的提问来源于stack exchange,提问作者Nazar
相关产品推荐
相关产品推荐

