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

基于查询字符串参数的通用过滤:API架构改造问询

改造API以支持通用查询能力

我来帮你搞定这个通用查询API的改造问题——硬编码一堆查询参数确实太麻烦了,新增个查询条件就得改方法签名,扩展性极差。下面是一套落地的方案,让你的API具备灵活的通用查询能力:

核心思路

放弃硬编码单个查询参数,改用通用查询DTO接收所有可能的查询条件,然后在业务层动态构建LINQ查询语句。这样不管后续新增多少查询维度,都不用修改API的方法签名,完全解耦查询需求和接口定义。

具体实现步骤

1. 创建通用查询DTO

先定义一个包含所有可能查询条件的DTO,同时加入分页、排序这类通用参数:

public class CompanyIPTQueryDto
{
    // 原有查询参数
    public int? CompanyId { get; set; }
    public int? TaskId { get; set; }
    public int? CompletedById { get; set; }
    public DateTime? DateCompletedStart { get; set; }
    public DateTime? DateCompletedEnd { get; set; }
    
    // 通用分页参数
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 10;
    
    // 通用排序参数
    public string SortBy { get; set; } = "DateCompleted";
    public string SortDirection { get; set; } = "desc";
}

2. 修改API端点

把原来的多个参数替换成这个查询DTO,用[FromQuery]标记让ASP.NET Core自动绑定URL查询参数:

[HttpGet]
[Route("Companies/{id:int}/Implementations/Tasks")]
public async Task<IApiResponse<List<CompanyIPTDTO>>> GetCompanyIPTs(
    int id, 
    [FromQuery] CompanyIPTQueryDto queryDto)
{
    // 把路由里的CompanyId传入DTO,避免重复参数
    queryDto.CompanyId = id;
    
    var results = await _companyService.GetCompanyIPTsAsync(queryDto);
    return ApiResponse.Success(results);
}

注意:这里把路由里的id直接赋值给DTO的CompanyId,保持接口URL的语义一致性

3. 动态构建LINQ查询

在业务逻辑层,基于DTO的非空参数动态拼接查询条件。以EF Core为例:

public async Task<List<CompanyIPTDTO>> GetCompanyIPTsAsync(CompanyIPTQueryDto queryDto)
{
    IQueryable<CompanyIPT> query = _dbContext.CompanyIPTs;
    
    // 动态添加过滤条件
    if (queryDto.TaskId.HasValue)
        query = query.Where(t => t.TaskId == queryDto.TaskId.Value);
    
    if (queryDto.CompletedById.HasValue)
        query = query.Where(t => t.CompletedById == queryDto.CompletedById.Value);
    
    if (queryDto.DateCompletedStart.HasValue)
        query = query.Where(t => t.DateCompleted >= queryDto.DateCompletedStart.Value);
    
    if (queryDto.DateCompletedEnd.HasValue)
        query = query.Where(t => t.DateCompleted <= queryDto.DateCompletedEnd.Value);
    
    // 动态排序
    query = queryDto.SortDirection.Equals("desc", StringComparison.OrdinalIgnoreCase)
        ? query.OrderByDescending(GetSortExpression(queryDto.SortBy))
        : query.OrderBy(GetSortExpression(queryDto.SortBy));
    
    // 分页处理
    query = query.Skip((queryDto.PageNumber - 1) * queryDto.PageSize)
                 .Take(queryDto.PageSize);
    
    // 映射为DTO返回
    return await query.ProjectTo<CompanyIPTDTO>(_mapper.ConfigurationProvider).ToListAsync();
}

// 辅助方法:根据排序字段构建表达式
private Expression<Func<CompanyIPT, object>> GetSortExpression(string sortBy)
{
    return sortBy.ToLower() switch
    {
        "taskid" => t => t.TaskId,
        "completedbyid" => t => t.CompletedById,
        "datecompleted" => t => t.DateCompleted,
        _ => t => t.DateCompleted // 默认排序
    };
}

额外优化建议

  • 参数验证:给查询DTO加上DataAnnotations验证属性(比如[Range(1, int.MaxValue)]给PageNumber),在API层用[ValidateModel]或者手动验证,避免无效参数。
  • 简化动态查询:如果觉得手动拼接Where条件太繁琐,可以使用System.Linq.Dynamic.Core库,直接用字符串构建过滤和排序表达式(比如query.Where("TaskId == @0", queryDto.TaskId))。
  • 查询安全性:避免直接暴露数据库字段作为查询参数,或者在构建查询时做白名单校验,防止恶意构造查询条件。
  • 返回分页结果:把返回的List<CompanyIPTDTO>改成包含总条数、当前页码的分页DTO,让前端更方便处理分页逻辑。

内容的提问来源于stack exchange,提问作者tokyo0709

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:16:26