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

如何在C#中使用Linq Skip和Take实现负页码的分页功能

实现C#中支持负页码的Linq分页功能

刚好做过类似的需求,咱们可以通过封装Linq扩展方法来优雅实现这个负页码分页逻辑——核心思路就是把负页码转换成对应的正页码,再用Skip和Take完成分页。

先理清楚规则:

  • 负页码代表从数据集末尾倒数的页码,比如总记录15、每页5条时,-1对应最后一页(第3页),-2对应倒数第二页(第2页),以此类推
  • 如果负页码的绝对值超过总页数,自动 fallback 到第一页;正页码超过总页数则 fallback 到最后一页

封装扩展方法

我写了两个版本,分别适配IEnumerable(内存集合)和IQueryable(EF Core等ORM场景,避免提前全表扫描):

public static class PaginationExtensions
{
    // 针对内存集合的版本
    public static IEnumerable<T> Paginate<T>(this IEnumerable<T> source, int pageIndex, int pageSize)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));
        if (pageSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(pageSize), "每页大小必须大于0");

        var totalRecords = source.Count();
        var totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);

        int actualPage;
        if (pageIndex < 0)
        {
            // 把负页码转为正页码:总页数 + 负页码 + 1
            actualPage = totalPages + pageIndex + 1;
            // 确保页码不小于1,避免绝对值过大的情况
            actualPage = Math.Max(actualPage, 1);
        }
        else
        {
            actualPage = pageIndex;
            // 确保页码不超过总页数
            actualPage = Math.Min(actualPage, totalPages);
        }

        if (totalRecords == 0)
            return Enumerable.Empty<T>();

        var skipCount = (actualPage - 1) * pageSize;
        skipCount = Math.Max(skipCount, 0);

        return source.Skip(skipCount).Take(pageSize);
    }

    // 针对IQueryable的版本(适合EF Core等ORM,生成SQL分页语句)
    public static IQueryable<T> Paginate<T>(this IQueryable<T> source, int pageIndex, int pageSize)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));
        if (pageSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(pageSize), "每页大小必须大于0");

        var totalRecords = source.Count();
        var totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);

        int actualPage;
        if (pageIndex < 0)
        {
            actualPage = totalPages + pageIndex + 1;
            actualPage = Math.Max(actualPage, 1);
        }
        else
        {
            actualPage = pageIndex;
            actualPage = Math.Min(actualPage, totalPages);
        }

        if (totalRecords == 0)
            return source.Take(0);

        var skipCount = (actualPage - 1) * pageSize;
        skipCount = Math.Max(skipCount, 0);

        return source.Skip(skipCount).Take(pageSize);
    }
}

示例用法

用你提到的场景测试一下:

// 模拟15条记录(1到15)
var records = Enumerable.Range(1, 15);
int pageSize = 5;

// 获取最后一页(页码-1)
var lastPage = records.Paginate(-1, pageSize); // 结果:11,12,13,14,15 → 对应Skip(10).Take(5)

// 获取倒数第二页(页码-2)
var secondLastPage = records.Paginate(-2, pageSize); // 结果:6,7,8,9,10 → 对应Skip(5).Take(5)

// 获取倒数第三页(页码-3)
var thirdLastPage = records.Paginate(-3, pageSize); // 结果:1,2,3,4,5 → 对应Skip(0).Take(5)

// 边界测试:负页码绝对值超过总页数(总页数3)
var fallbackToFirstPage = records.Paginate(-4, pageSize); // 结果:1-5

关键逻辑说明

  1. 总页数计算:用Math.Ceiling向上取整,确保不满一页的记录也单独算一页(比如16条记录、每页5条,总页数是4)
  2. 负页码转换:公式totalPages + pageIndex + 1的逻辑是把负页码映射到从后往前的正页码,比如总页数3,-1→3,-2→2,完美匹配需求
  3. 边界防护:处理了空集合、非法页大小、页码超出范围等异常情况,保证方法的健壮性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 22:42:53