本节内容,部分为补充内容,部分涉及到5.2(P131-133)。主要NuGet包:如前章节所述
仓储模式,将数据访问层抽象出来,隐藏了底层对数据源的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倍
标签:Core,ASP,return,EFCore2.9,ctx,仓储,Employee,e1,public From: https://www.cnblogs.com/functionMC/p/16839799.html