You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

电商项目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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 15:23:12