如何在ASP.NET Core WebAPI中统一实现通用CRUD以避免代码重复
避免ASP.NET Core WebAPI重复CRUD代码的解决方案
完全不需要在每个实体控制器中重复编写这些通用CRUD代码,下面是两种落地性强的方案,能帮你彻底消除重复逻辑:
1. 泛型基类控制器(最直接的方案)
先定义一个通用实体接口,约束所有业务实体必须包含通用属性(比如主键Id、创建时间CreatedTime):
public interface IBaseEntity { int Id { get; set; } DateTime CreatedTime { get; set; } }
让你的User、Blog、News实体实现这个接口:
public class Blog : IBaseEntity { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime CreatedTime { get; set; } // 其他业务属性 }
接下来创建泛型基控制器,把所有通用CRUD逻辑封装进去:
[ApiController] [Route("api/[controller]")] public class BaseCrudController<TEntity> : ControllerBase where TEntity : class, IBaseEntity { protected readonly DbContext _dbContext; protected readonly DbSet<TEntity> _dbSet; public BaseCrudController(DbContext dbContext) { _dbContext = dbContext; _dbSet = dbContext.Set<TEntity>(); } // Read by ID [HttpGet("{id}")] public async Task<ActionResult<TEntity>> GetById(int id) { var entity = await _dbSet.FindAsync(id); return entity == null ? NotFound() : Ok(entity); } // Read by page [HttpGet("page")] public async Task<ActionResult<IEnumerable<TEntity>>> GetByPage( int pageIndex = 1, int pageSize = 10) { var entities = await _dbSet .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync(); return Ok(entities); } // Read by page with sorting [HttpGet("page/sorted")] public async Task<ActionResult<IEnumerable<TEntity>>> GetByPageWithSorting( string sortBy, bool isAscending = true, int pageIndex = 1, int pageSize = 10) { IQueryable<TEntity> query = _dbSet; if (!string.IsNullOrWhiteSpace(sortBy)) { var property = typeof(TEntity).GetProperty(sortBy); if (property != null) { query = isAscending ? query.OrderBy(e => property.GetValue(e)) : query.OrderByDescending(e => property.GetValue(e)); } } var entities = await query .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync(); return Ok(entities); } // Update with JSON Patch [HttpPatch("{id}")] public async Task<IActionResult> Update(int id, JsonPatchDocument<TEntity> patchDoc) { if (patchDoc == null) return BadRequest(); var entity = await _dbSet.FindAsync(id); if (entity == null) return NotFound(); patchDoc.ApplyTo(entity, ModelState); if (!ModelState.IsValid) return ValidationProblem(ModelState); try { await _dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!_dbSet.Any(e => e.Id == id)) return NotFound(); throw; } return NoContent(); } // Delete by ID [HttpDelete("{id}")] public async Task<IActionResult> DeleteById(int id) { var entity = await _dbSet.FindAsync(id); if (entity == null) return NotFound(); _dbSet.Remove(entity); await _dbContext.SaveChangesAsync(); return NoContent(); } // Delete entities created before a date [HttpDelete("old")] public async Task<IActionResult> DeleteOldEntities(DateTime cutoffDate) { var oldEntities = await _dbSet.Where(e => e.CreatedTime < cutoffDate).ToListAsync(); if (!oldEntities.Any()) return NoContent(); _dbSet.RemoveRange(oldEntities); await _dbContext.SaveChangesAsync(); return NoContent(); } }
最后,每个实体控制器只需要继承这个基类即可,无需重复写任何通用逻辑:
public class BlogsController : BaseCrudController<Blog> { public BlogsController(YourDbContext dbContext) : base(dbContext) { } // 这里只需要添加Blog专属的接口(如果有的话) }
2. 泛型仓储模式(进一步解耦数据访问)
如果想把数据访问逻辑从控制器中分离出来,让代码更符合单一职责原则,可以配合泛型仓储模式:
先定义泛型仓储接口和实现:
public interface IGenericRepository<TEntity> where TEntity : class, IBaseEntity { Task<TEntity> GetByIdAsync(int id); Task<IEnumerable<TEntity>> GetByPageAsync(int pageIndex, int pageSize); Task<IEnumerable<TEntity>> GetByPageWithSortingAsync(string sortBy, bool isAscending, int pageIndex, int pageSize); void ApplyPatch(TEntity entity, JsonPatchDocument<TEntity> patchDoc); Task DeleteByIdAsync(int id); Task DeleteOldEntitiesAsync(DateTime cutoffDate); Task SaveChangesAsync(); } public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, IBaseEntity { protected readonly DbContext _dbContext; protected readonly DbSet<TEntity> _dbSet; public GenericRepository(DbContext dbContext) { _dbContext = dbContext; _dbSet = dbContext.Set<TEntity>(); } public async Task<TEntity> GetByIdAsync(int id) => await _dbSet.FindAsync(id); public async Task<IEnumerable<TEntity>> GetByPageAsync(int pageIndex, int pageSize) { return await _dbSet .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync(); } public async Task<IEnumerable<TEntity>> GetByPageWithSortingAsync(string sortBy, bool isAscending, int pageIndex, int pageSize) { IQueryable<TEntity> query = _dbSet; if (!string.IsNullOrWhiteSpace(sortBy)) { var property = typeof(TEntity).GetProperty(sortBy); if (property != null) { query = isAscending ? query.OrderBy(e => property.GetValue(e)) : query.OrderByDescending(e => property.GetValue(e)); } } return await query .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync(); } public void ApplyPatch(TEntity entity, JsonPatchDocument<TEntity> patchDoc) { patchDoc.ApplyTo(entity); } public async Task DeleteByIdAsync(int id) { var entity = await _dbSet.FindAsync(id); if (entity != null) _dbSet.Remove(entity); } public async Task DeleteOldEntitiesAsync(DateTime cutoffDate) { var oldEntities = await _dbSet.Where(e => e.CreatedTime < cutoffDate).ToListAsync(); if (oldEntities.Any()) _dbSet.RemoveRange(oldEntities); } public async Task SaveChangesAsync() => await _dbContext.SaveChangesAsync(); }
然后修改基控制器,依赖泛型仓储:
[ApiController] [Route("api/[controller]")] public class BaseCrudController<TEntity> : ControllerBase where TEntity : class, IBaseEntity { protected readonly IGenericRepository<TEntity> _repository; public BaseCrudController(IGenericRepository<TEntity> repository) { _repository = repository; } // Read by ID [HttpGet("{id}")] public async Task<ActionResult<TEntity>> GetById(int id) { var entity = await _repository.GetByIdAsync(id); return entity == null ? NotFound() : Ok(entity); } // 其他方法同理,调用_repository的对应方法即可,此处省略重复代码 }
最后在Program.cs中注册泛型仓储:
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
关键注意事项
- 所有实体必须实现
IBaseEntity接口,确保泛型逻辑能正常识别通用属性。 - 如果某个实体需要修改通用逻辑(比如自定义分页规则、特殊排序字段),直接在对应控制器中重写基类方法即可。
- 使用JSON Patch需要安装NuGet包
Microsoft.AspNetCore.Mvc.NewtonsoftJson,并在Program.cs中配置:builder.Services.AddControllers().AddNewtonsoftJson();
内容的提问来源于stack exchange,提问作者AkiraVoid
相关产品推荐
相关产品推荐

