如何用ASP.NET Core和EF Core打造灵活可扩展的动态分页系统?

摘要:引言 欢迎阅读,这篇文章主要面向初级开发者。 在开始之前,先问你一个问题:你做的系统,是不是每次增加一个查询条件或者排序字段,都要去请求参数对象里加一个属性,然后再跑去改 EF Core 的查询逻辑? 如果是,那这篇文章应该对你有用。我会带
引言 欢迎阅读,这篇文章主要面向初级开发者。 在开始之前,先问你一个问题:你做的系统,是不是每次增加一个查询条件或者排序字段,都要去请求参数对象里加一个属性,然后再跑去改 EF Core 的查询逻辑? 如果是,那这篇文章应该对你有用。我会带你做一个统一的、扩展起来不那么麻烦的分页查询方案。整体思路是四件事:​统一入参、统一出参、动态排序、动态过滤​。 统一请求参数 先定义一个公共的 QueryParameters 解决这个问题: public class QueryParameters { private const int MaxPageSize = 100; private int _pageSize = 10; public int PageNumber { get; set; } = 1; // 限制最大值,防止前端传一个很大数值把数据库搞崩了 public int PageSize { get => _pageSize; set => _pageSize = value > MaxPageSize ? MaxPageSize : value; } // 支持多字段排序,格式:"name desc,price asc" public string? SortBy { get; set; } // 通用关键词搜索 public string? Search { get; set; } // 动态过滤条件 public List<FilterItem> Filters { get; set; } = []; // 要返回的字段,逗号分隔:"id,name,price",不传则返回全部 public string? Fields { get; set; } } ASP.NET Core 的模型绑定会自动把 query string 映射到这个对象,不需要手动解析。后续如果某个接口有额外参数,继承它加字段就行,不用每次从头定义。 统一响应包装器 返回值也统一一下,把分页信息和数据放在一起,调用方就不用自己拼了: public class PagedResponse<T> { // IReadOnlyList 防止外部随意修改集合 public IReadOnlyList<T> Data { get; init; } = []; public int PageNumber { get; init; } public int PageSize { get; init; } public int TotalRecords { get; init; } public int TotalPages => (int)Math.Ceiling(TotalRecords / (double)PageSize); public bool HasNextPage => PageNumber < TotalPages; public bool HasPreviousPage => PageNumber > 1; } Data 是任意类型的集合,用 IReadOnlyList 防止被意外修改。TotalPages、HasNextPage 和 HasPreviousPage 三个是计算属性,不需要单独赋值。 扩展方法 把分页、排序、过滤都做成 IQueryable<T> 的扩展方法,用起来像链式调用,调用的地方看起来会很干净。 分页 public static IQueryable<T> ApplyPagination<T>( this IQueryable<T> query, int pageNumber, int pageSize) { return query .Skip((pageNumber - 1) * pageSize) .Take(pageSize); } 动态排序 解析 "name desc,price asc" 这样的字符串,动态生成排序表达式。
阅读全文