魅力程序猿

  • 首页
  • Java
  • Android
  • APP
    • 扑克计分器
    • Video Wallpaper
  • 联系我
  • 关于我
  • 资助
道子
向阳而生
  1. 首页
  2. AI技术
  3. 正文

开发实战:asp.net core + ef core 实现动态可扩展的分页方案

2026年4月6日 6点热度 0人点赞 0条评论

📰 来源: 博客园


欢迎阅读,这篇文章主要面向初级开发者。

在开始之前,先问你一个问题:你做的系统,是不是每次增加一个查询条件或者排序字段,都要去请求参数对象里加一个属性,然后再跑去改 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" 这样的字符串,动态生成排序表达式。用反射就能做到,不需要额外的库:

public static IQueryable<T> ApplySort<T>(
    this IQueryable<T> query,
    string? sortBy)
{
    if (string.IsNullOrWhiteSpace(sortBy))
        return query;

    var orderParams = sortBy.Split(',', StringSplitOptions.RemoveEmptyEntries);
    var isFirst = true;

    foreach (var param in orderParams)
    {
        var parts = param.Trim().Split(' ');
        var propertyName = parts[0];
        var isDesc = parts.Length > 1
            && parts[1].Equals("desc", StringComparison.OrdinalIgnoreCase);

        // 用反射找属性,找不到就跳过,避免抛异常
        var prop = typeof(T).GetProperty(
            propertyName,
            BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

        if (prop == null) continue;

        // 构建表达式树:x => x.PropertyName
        var paramExpr = Expression.Parameter(typeof(T), "x");
        var body = Expression.Property(paramExpr, prop);
        var lambda = Expression.Lambda(body, paramExpr);

        var methodName = isFirst
            ? (isDesc ? "OrderByDescending" : "OrderBy")
            : (isDesc ? "ThenByDescending" : "ThenBy");

        var method = typeof(Queryable).GetMethods()
            .First(m => m.Name == methodName && m.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T), prop.PropertyType);

        query = (IQueryable<T>)method.Invoke(null, [query, lambda])!;
        isFirst = false;
    }

    return query;
}

也可以考虑 System.Linq.Dynamic.Core 这个库。

这是扩展性最强的一块。前端传字段名 + 操作符 + 值,后端用表达式树动态拼 Where 条件,不需要每加一个筛选项就改后端代码。

先定义过滤条件的数据结构:

public class FilterItem
{
// 字段名,对应实体属性,不区分大小写
public string Field { get; set; } = string.Empty;

// 操作符:eq、neq、con


🔗 原文链接: 点击阅读原文

标签: AI 人工智能 技术博客
最后更新:2026年4月6日

daozi

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
搜索
联系方式

QQ群:179730949
QQ群:114559024
欢迎您加入Android大家庭
本人QQ:136049925

赐我一丝安慰
给我一点鼓励

COPYRIGHT © 2023 魅力程序猿. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

豫ICP备15000477号