《ASP.NET Core技术内幕与项目实战》精简集-EFCore2.9:泛型仓储实现IRepository
仓储模式,将数据访问层抽象出来,隐藏了底层对数据源的CRUD操作,这样在应用层或控制器中,我们直接访问仓储封装的方法即可,不需和数据源直接接触。泛型仓储以面向接口和泛型方式实现,一方面,可以非常方便的更换数据源,而业务代码不需要做任何修改,实现解耦;另一方面只要创建一个基础泛型仓储,就可以实现所有实体的CRUD操作,使用非常方便。PS:仓储实现,是接口和泛型应用的经典案例,虽然ABP、Furion、MASA等AspNetCore框架,都定义了自己的泛型仓储,可以直接使用,但理解和掌握其原理,仍然非常重要。
一、非泛型仓储的实现
以下案例,我们创建一个员工类的仓储接口,并实现一个内存数据源仓储和一个SQL数据源仓储,内存数据源和SQL数据源可以随意切换,而控制器方法不需要做任何改动。之前章节,已经学习过DbContext、数据库迁移、DbContext服务注册等,以下案例略过这些知识点。另外,为了简化代码,案例的逻辑并不严谨,首要还是学习仓储的实现原理,不需要过度关注细节。
//1、创建实体类Employee,做测试使用========================================================================
public class Employee
{
public int Id { get; set; }
public string? Name { get; set; }
public int Age { get; set; }
}
//2、创建仓储接口=========================================================================================
public interface IEmployeeRepository
{
Employee AddEmployee(Employee employee);//新增员工
Employee DeleteEmployeeById(int id);//删除指定员工
Employee UpdateEmployee(int id, Employee employee);//更新员工
Employee GetEmployeeById(int id);//获取指定Id员工
IEnumerable<Employee> GetAllEmployees();//获取所有员工
}
//3-1、创建内存数据源的仓储实现===============================================================================
public class MockEmployeeRepository : IEmployeeRepository
{
//定义一个内存数据源
private readonly List<Employee> employees;
public MockEmployeeRepository()
{
this.employees = new List<Employee>()
{
new Employee { Id = 1, Name = "zs1", Age = 25},
new Employee { Id = 2, Name = "ls1", Age = 32},
new Employee { Id = 3, Name = "ww1", Age = 23},
new Employee { Id = 4, Name = "zl1", Age = 42},
new Employee { Id = 5, Name = "qq1", Age = 48}
};
}
//添加员工
public Employee AddEmployee(Employee employee)
{
employees.Add(employee);
return employee;
}
//删除员工
public Employee DeleteEmployeeById(int id)
{
var employee = employees.FirstOrDefault(e=>e.Id == id);
if (employee != null)
{
employees.Remove(employee);
}
return employee;
}
//更新员工
public Employee UpdateEmployee(int id,Employee employee)
{
var e1 = employees.FirstOrDefault(e=>e.Id == id);
if (e1 != null)
{
e1.Name = employee.Name;
e1.Age = employee.Age;
}
return e1;
}
//获取所有员工
public IEnumerable<Employee> GetAllEmployees()
{
return employees;
}
//获取指定员工
public Employee GetEmployeeById(int id)
{
return employees.FirstOrDefault(e=>e.Id == id);
}
}
//3-2、创建SQL数据源的仓储实现===============================================================================
public class SQLEmployeeRepository : IEmployeeRepository
{
//注入DbContext
private readonly MyDbContext ctx;
public SQLEmployeeRepository(MyDbContext ctx)
{
this.ctx = ctx;
}
//添加员工
public Employee AddEmployee(Employee employee)
{
ctx.Employees.Add(employee);
ctx.SaveChanges();
return employee;
}
//删除员工
public Employee DeleteEmployeeById(int id)
{
var e1 = ctx.Employees.FirstOrDefault(e=>e.Id == id);
if (e1 != null)
{
ctx.Employees.Remove(e1);
ctx.SaveChanges();
}
return e1;
}
//更新员工
public Employee UpdateEmployee(int id, Employee employee)
{
var e1 = ctx.Employees.FirstOrDefault(e => e.Id == id);
e1.Name = employee.Name;
e1.Age = employee.Age;
ctx.SaveChanges();
return e1;
}
//获取所有员工
public IEnumerable<Employee> GetAllEmployees()
{
return ctx.Employees;
}
//获取指定员工
public Employee GetEmployeeById(int id)
{
return ctx.Employees.Find(id);
}
}
//4、在容器中注册仓储,使用SQL数据源(AspNetCore WebAPI应用)===================================================
builder.Services.AddScoped<IEmployeeRepository,SQLEmployeeRepository>();
//如果要更换为内存数据源,可更换为以下代码
//builder.Services.AddScoped<IEmployeeRepository,MockEmployeeRepository>();
//5、创建一个控制器,直接使用仓储==============================================================================
//即使切换数据源,或者修改仓储实现,以下业务代码也不需要做任何修改
//但是,如果修改了仓储接口,以下业务代码可能需要修改
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
//注入仓储
private readonly IEmployeeRepository employeeRepository;
public TestController(IEmployeeRepository employeeRepository)
{
this.employeeRepository = employeeRepository;
}
//添加员工
[HttpPost]
public ActionResult<Employee> AddEmployee(Employee employee)
{
var e1 = employeeRepository.AddEmployee(employee);
if (e1 != null)
{
return e1;
}
return BadRequest("添加失败");
}
//删除员工
[HttpDelete]
public ActionResult<Employee> DeleteEmployeeById(int id)
{
var e1 = employeeRepository.DeleteEmployeeById(id);
if (e1 != null)
{
return e1;
}
return BadRequest("删除失败");
}
//更新员工
[HttpDelete]
public ActionResult<Employee> UpdateEmployee(int id, Employee employee)
{
var e1 = employeeRepository.UpdateEmployee(id,employee);
if (e1 != null)
{
return e1;
}
return BadRequest("更新失败");
}
//获取指定员工
[HttpGet]
public ActionResult<Employee> GetEmployeeById(int id)
{
var e1 = employeeRepository.GetEmployeeById(id);
if (e1 != null)
{
return e1;
}
return NotFound("找不到指定员工");
}
//获取所有员工
[HttpGet]
public ActionResult<IEnumerable<Employee>> GetAllEmployees()
{
var employees = employeeRepository.GetAllEmployees();
if (employees.Any() == true)
{
return employees.ToList();
}
return NotFound("找不到员工");
}
}
二、泛型仓储的实现
仓储模式,隐藏了底层数据的CRUD操作,通过仓储接口实现了系统的解耦。但是,如果应用中有很多实体,按照上例中的套路,每个实体都要建立相应的仓储接口和实现,且仓储的操作方法基本一样,这将带来大量的重复性工作。所以,我们引入泛型仓储,只需要定义一个泛型仓储和实现,就可以实现所有实体的仓储操作。下面案例实现了一个简单的泛型仓储,定义的所有方法都使用异步方法。
1、创建泛型仓储接口
//泛型参数:TEntity为仓储的实体类型,TPrimaryKey为仓储的主键类型
public interface IRepository<TEntity,TPrimaryKey> where TEntity : class
{
//新增实体
Task<TEntity> AddAsync(TEntity entity);
//更新实体
Task<TEntity> UpdateAsync(TEntity entity);
//删除实体
Task DeleteAsync(TEntity entity);//删除一个实体
Task DeleteAsync(Expression<Func<TEntity,bool>> predicate);//删除符合条件的多个实体
//查询实体
Task<List<TEntity>> GetAllListAsync();//查询所有
Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);//查询指定条件的多条
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);//查询指定条件的一条,如果没有,返回null
Task<int> CountAsync();//查询全部总数
Task<int> CountAsync(Expression<Func<TEntity,bool>> predicate);//查询指定条件总数
}
2、实现泛型仓储接口,使用SQL数据源
public class RepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : class
{
//准备①:注入DbContext
private readonly MyDbContext ctx;
public RepositoryBase(MyDbContext ctx)
{
this.ctx = ctx;
}
//准备②:获取泛型实体的DbSet,注意使用虚方法,传入具体的实体类型时,重写这个DbSet
public virtual DbSet<TEntity> dbSet => ctx.Set<TEntity>();
//准备③:检查实体是否处理跟踪状态,如果是,则返回实体;如果不是,则添加跟踪状态
protected virtual void AttachIfNot(TEntity entity)
{
var entry = ctx.ChangeTracker.Entries().FirstOrDefault(e=>e.Entity == entity);
if (entry != null)
{
return;
}
dbSet.Attach(entity);
}
//实现新增实体
public async Task<TEntity> AddAsync(TEntity entity)
{
var entityResult = await dbSet.AddAsync(entity);
await ctx.SaveChangesAsync();
return entityResult.Entity;
}
//实现更新实体。ctx.Entry(entity).State获取实体跟踪状态
public async Task<TEntity> UpdateAsync(TEntity entity)
{
AttachIfNot(entity);
ctx.Entry(entity).State = EntityState.Modified;
await ctx.SaveChangesAsync();
return entity;
}
//实体删除实体
//删除一个实体
public async Task DeleteAsync(TEntity entity)
{
AttachIfNot(entity);
var entityResult = dbSet.Remove(entity);
await ctx.SaveChangesAsync();
}
//删除指定条件的多个实体。dbSet.AsQueryable()方法获取实体的Queryable集合
public async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
{
foreach (var entity in dbSet.AsQueryable().Where(predicate).ToList())
{
await DeleteAsync(entity);
};
}
//实现查询
//查询所有
public async Task<List<TEntity>> GetAllListAsync()
{
return await dbSet.AsQueryable().ToListAsync();
}
//查询指定条件的多条
public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
{
return await dbSet.AsQueryable().Where(predicate).ToListAsync();
}
//查询指定条件的首条
public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
{
return await dbSet.AsQueryable().FirstOrDefaultAsync(predicate);
}
//实现查询总数
public async Task<int> CountAsync()
{
return await dbSet.AsQueryable().CountAsync();
}
//查询指定条件的总数
public async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
{
return await dbSet.AsQueryable().Where(predicate).CountAsync();
}
}
3、在容器中注册泛型仓储服务
builder.Services.AddTransient(typeof(IRepository<,>),typeof(RepositoryBase<,>));
4、在控制器中使用泛型仓储
public class TestController : ControllerBase
{
//注入泛型仓储,指定泛型仓储的实体类型和主键类型
private readonly IRepository<Employee, int> employeeRepository;
public TestController(IRepository<Employee,int> employeeRepository)
{
this.employeeRepository = employeeRepository;
}
//查询指定条件的员工
[HttpGet]
public async Task<ActionResult<List<Employee>>> GetEmployeesMaxAge(int maxAge)
{
return await employeeRepository.GetAllListAsync(e=>e.Age <= maxAge);
}
}
特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍

浙公网安备 33010602011771号