基于查询字符串参数的通用过滤: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
相关产品推荐
相关产品推荐

