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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 21:30:42