仓储模式是领域驱动设计(DDD) 和 .NET 项目中最常用的设计模式之一,核心作用是隔离业务逻辑与数据访问层,让代码更易维护、测试和扩展。
一、什么是仓储模式?
核心概念
- 仓储(Repository) :充当业务逻辑 和数据库之间的中介,类似内存中的集合。
- 作用 :业务层只调用仓储方法,不需要关心数据是怎么存/取的(EF Core、Dapper、内存都可以)。
- 优势 :
- 解耦:数据访问逻辑和业务逻辑分离
- 可测试:无需真实数据库即可单元测试
- 统一数据访问规范,代码更整洁
- 易于切换数据访问技术(EF Core ↔ Dapper)
标准结构
arduino
项目
├─ Models/Entities // 实体类(对应数据库表)
├─ Data // 数据库上下文
├─ Repositories
│ ├─ IRepository.cs // 泛型仓储接口(基础CRUD)
│ ├─ Repository.cs // 泛型仓储实现
│ ├─ IUserRepo.cs // 特定实体仓储接口
│ └─ UserRepo.cs // 特定实体仓储实现
└─ Services/Controllers // 调用仓储
二、快速实现(.NET Core + EF Core)
1. 准备工作
安装 NuGet 包:
bash
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
2. 定义实体(示例:User)
csharp
// Entities/User.cs
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
3. 创建数据库上下文
csharp
// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
}
4. 定义泛型仓储接口(所有实体通用)
csharp
// Repositories/IRepository.cs
using System.Linq.Expressions;
public interface IRepository<T> where T : class
{
// 查询
Task<T> GetByIdAsync(int id);
Task<List<T>> GetAllAsync();
Task<List<T>> FindAsync(Expression<Func<T, bool>> predicate);
// 新增/修改/删除
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
// 保存
Task SaveChangesAsync();
}
5. 实现泛型仓储基类
csharp
// Repositories/Repository.cs
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
public class Repository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _dbContext;
private readonly DbSet<T> _dbSet;
// 依赖注入 DbContext
public Repository(AppDbContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
public async Task<T> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<List<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task<List<T>> FindAsync(Expression<Func<T, bool>> predicate)
{
return await _dbSet.Where(predicate).ToListAsync();
}
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public Task UpdateAsync(T entity)
{
_dbSet.Update(entity);
return Task.CompletedTask;
}
public Task DeleteAsync(T entity)
{
_dbSet.Remove(entity);
return Task.CompletedTask;
}
public async Task SaveChangesAsync() => await _dbContext.SaveChangesAsync();
}
6. 特定实体仓储(可选,扩展通用方法)
csharp
// IUserRepository.cs
public interface IUserRepository : IRepository<User>
{
// 扩展:根据姓名查询用户
Task<User> GetUserByNameAsync(string name);
}
// UserRepository.cs
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(AppDbContext dbContext) : base(dbContext) { }
public async Task<User> GetUserByNameAsync(string name)
{
return await _dbContext.Users.FirstOrDefaultAsync(u => u.Name == name);
}
}
7. 注册依赖注入(Program.cs)
csharp
// 注册数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 注册泛型仓储 + 特定仓储
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<IUserRepository, UserRepository>();
8. 在 Controller 中使用仓储
csharp
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly IUserRepository _userRepository;
// 构造函数注入
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var user = await _userRepository.GetByIdAsync(id);
return Ok(user);
}
[HttpPost]
public async Task<IActionResult> Add(User user)
{
await _userRepository.AddAsync(user);
await _userRepository.SaveChangesAsync();
return Created("", user);
}
}
三、进阶:工作单元模式(Unit of Work)
工作单元 + 仓储 是企业级开发的最佳组合 ,解决多仓储事务统一提交问题。
1. IUnitOfWork 接口
csharp
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
Task<int> SaveChangesAsync();
}
2. UnitOfWork 实现
csharp
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _dbContext;
public IUserRepository Users { get; }
public UnitOfWork(AppDbContext dbContext)
{
_dbContext = dbContext;
Users = new UserRepository(_dbContext);
}
public async Task<int> SaveChangesAsync() => await _dbContext.SaveChangesAsync();
public void Dispose() => _dbContext.Dispose();
}
3. 注册 + 使用
csharp
// Program.cs
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
// Controller
private readonly IUnitOfWork _uow;
public UserController(IUnitOfWork uow) => _uow = uow;
// 调用
await _uow.Users.AddAsync(user);
await _uow.SaveChangesAsync(); // 统一提交
四、仓储模式最佳实践
- 不要滥用:简单单表操作,直接用泛型仓储即可;复杂业务再建专用仓储。
- IQueryable 慎用 :不要在接口返回
IQueryable,会破坏隔离性,推荐返回List/Task<T>。 - 搭配工作单元:多表操作必须用 UoW 保证事务一致性。
- 依赖注入:始终使用 DI 注入仓储,不要手动 new。
- 异步优先 :.NET Core 全程使用
async/await提升性能。
五、适用场景
- 中大型企业应用
- 需要单元测试的项目
- 数据访问层需要灵活切换(SQL/MySQL/内存)
- 多人协作、规范统一的项目
总结
- 仓储模式 = 数据访问层封装,解耦业务与数据库
- 泛型仓储提供通用 CRUD,专用仓储扩展个性化方法
- 工作单元解决多仓储事务提交问题
- .NET Core 中通过依赖注入使用,代码简洁易维护