个人博客

记录和分享IT工作、学习日常

在ASP.NET Core Web Application中如何实现分页显示数据库记录


0 | Cable Fan | 01/01/0001 00:00:00 | 11/06/2025 16:49:13 | 01/01/0001 00:00:00

【AI】在ASP.NET Core Web Application中如何实现分页显示数据库记录。这是一个比较实用的技术问题,用户可能正在开发需要处理大量数据的Web应用,需要高效的分页方案来提升用户体验和系统性能。 我考虑从几个核心方面来构建这个回答:首先是SQL查询分页和内存分页这两种主要的技术路线,然后需要涵盖具体的实现步骤,最后还应该提供一些最佳实践建议。根据搜索结果,我看到一个比较完整的示例展示了通过计算起始位置和结束位置来实现SQL分页,这个思路很清晰值得参考。而则介绍了PagedDataSource类的使用方法,这正好可以作为内存分页的代表方案。 虽然主要讨论WebAPI场景,但其中关于Entity Framework Core的Skip和Take方法的使用说明非常准确,这正是ASP.NET Core中常用的分页方式。至于和,它们提供了针对不同数据库的特定分页语法,比如Oracle的ROWNUM和SQL Server的ROW_NUMBER,这些信息对用户处理多数据库支持会很有帮助。 我打算先用一个表格来概括两种主要分页方式的特点和适用场景,这样用户能够快速做出选择。接着需要详细说明每种方式的实现步骤,包括具体的代码示例。最后还要提醒用户一些常见的注意事项,比如性能优化和安全性考虑。 在整理具体内容时,我会重点参考和中的代码示例,同时融入关于数据库差异的处理建议,确保提供的解决方案既全面又实用。

ASP.NET Core Web Application 中实现分页能提升用户体验和应用性能。这里为你提供两种主流实现方式:基于 SQL 查询的分页(性能更好)和内存分页(实现简单),以及一个实用的分页辅助类

下面这个表格能帮你快速了解两种主要分页方式的特点:

特性 SQL查询分页 (推荐大数据量) 内存分页 (适合小数据量)
性能 ,仅查询当前页数据-1 较低,需先加载所有数据-2
适用场景 数据量大的情况-1 数据量小(如2万条内)-2
实现思路 SQL语句(如OFFSET FETCH)直接分页-1 从数据库获取全部数据,代码中分页-2
优点 响应快,数据库压力小 实现简单,适合数据量稳定且小的场景-2
缺点 SQL语句可能稍复杂 数据量大时内存和网络压力大-2

🔧 实现分页的详细步骤

使用SQL查询分页(推荐)

这种方法的核心是让数据库只返回当前页需要的数据。

  1. 创建分页参数模型
    在模型中,我们通常需要知道当前是第几页以及每页显示多少条记录。

    csharp
     
     
    public class PaginationParams
    {
        private const int MaxPageSize = 50; // 防止请求过多数据
        private int _pageSize = 10; // 默认每页10条
        
        public int PageNumber { get; set; } = 1; // 当前页码,默认第1页
        public int PageSize
        {
            get => _pageSize;
            set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;
        }
    }
  2. 修改控制器与Action
    在控制器中,我们接收分页参数,并传递给服务层或数据访问层。

    csharp
     
     
    [HttpGet]
    public async Task<IActionResult> GetUsers(PaginationParams paginationParams)
    {
        // 将分页参数传递给服务方法,获取分页后的数据
        var pagedUsers = await _userService.GetPagedUsersAsync(paginationParams);
        return Ok(pagedUsers);
    }
  3. 服务层或数据访问层处理分页逻辑
    这里以Entity Framework Core为例,使用 SkipTake 方法生成分页查询-3

    csharp
     
     
    public async Task<PagedResult<User>> GetPagedUsersAsync(PaginationParams paginationParams)
    {
        var query = _context.Users.AsNoTracking(); // AsNoTracking提升查询性能
        
        // 先获取总记录数,用于计算总页数
        var totalCount = await query.CountAsync();
        
        // 然后获取当前页的数据
        var items = await query
            .OrderBy(u => u.Id) // 必须指定排序
            .Skip((paginationParams.PageNumber - 1) * paginationParams.PageSize)
            .Take(paginationParams.PageSize)
            .ToListAsync();
            
        return new PagedResult<User>(items, totalCount, paginationParams.PageNumber, paginationParams.PageSize);
    }
  4. 返回分页结果
    定义一个通用的分页结果类,方便前端使用。

    csharp
     
     
    public class PagedResult<T>
    {
        public List<T> Items { get; set; } // 当前页的数据列表
        public int TotalCount { get; set; } // 总记录数
        public int PageNumber { get; set; } // 当前页码
        public int PageSize { get; set; } // 每页记录数
        public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); // 计算总页数
        public bool HasPrevious => PageNumber > 1;
        public bool HasNext => PageNumber < TotalPages;
        
        public PagedResult(List<T> items, int totalCount, int pageNumber, int pageSize)
        {
            Items = items;
            TotalCount = totalCount;
            PageNumber = pageNumber;
            PageSize = pageSize;
        }
    }

使用内存分页

如果你的数据量确实很小,并且确定要一次性加载,可以使用 PagedDataSource 进行分页,但请注意其性能局限-2

csharp
 
 
// 注意:此方法需一次性加载所有数据,请谨慎用于大数据量场景:cite[2]
public async Task<PagedResult<User>> GetPagedUsersInMemory(PaginationParams paginationParams)
{
    var allUsers = await _context.Users.ToListAsync(); // 获取所有数据
    
    var pagedDataSource = new PagedDataSource
    {
        DataSource = allUsers,
        AllowPaging = true,
        CurrentPageIndex = paginationParams.PageNumber - 1,
        PageSize = paginationParams.PageSize
    };
    
    return new PagedResult<User>(
        pagedDataSource.Cast<User>().ToList(),
        allUsers.Count,
        paginationParams.PageNumber,
        paginationParams.PageSize
    );
}

📄 前端显示与分页导航

获取到分页数据后,你需要在前端显示分页导航。一种常见做法是将分页信息放在响应Header中(如X-Pagination),前端JavaScript解析后生成分页按钮。

这里以直接在视图中(Razor Pages 或 MVC)生成为例:

html
 
 
 
 
@model PagedResult<User>

<!-- 显示数据 -->
<table class="table">
  @foreach (var user in Model.Items)
  {
    <tr>
      <td>@user.Name</td>
    </tr>
  }
</table>

<!-- 分页导航 -->
<nav>
  <ul class="pagination">
    <!-- 上一页 -->
    @if (Model.HasPrevious)
    {
      <li class="page-item">
        <a class="page-link" href="@Url.Action("GetUsers", new { pageNumber = Model.PageNumber - 1, pageSize = Model.PageSize })">上一页</a>
      </li>
    }

    <!-- 页码 -->
    @for (int i = 1; i <= Model.TotalPages; i++)
    {
      <li class="page-item @(i == Model.PageNumber ? "active" : "")">
        <a class="page-link" href="@Url.Action("GetUsers", new { pageNumber = i, pageSize = Model.PageSize })">@i</a>
      </li>
    }

    <!-- 下一页 -->
    @if (Model.HasNext)
    {
      <li class="page-item">
        <a class="page-link" href="@Url.Action("GetUsers", new { pageNumber = Model.PageNumber + 1, pageSize = Model.PageSize })">下一页</a>
      </li>
    }
  </ul>
</nav>

💡 进阶分页辅助类与最佳实践

  1. 考虑使用第三方分页库
    X.PagedList 这样的库可以简化很多工作。你可以通过NuGet安装 X.PagedList.Mvc.Core

    csharp
     
     
    public async Task<IPagedList<User>> GetPagedListAsync(int pageNumber, int pageSize)
    {
        return await _context.Users.ToPagedListAsync(pageNumber, pageSize);
    }
  2. 数据库特定分页语法
    对于超大规模数据,深入研究数据库提供的分页语法很有帮助。例如,SQL Server 可使用 ROW_NUMBER()-8OFFSET-FETCH;Oracle 可使用 ROWNUM-5ROW_NUMBER()

  3. 最佳实践与注意事项

    • 必须指定排序:使用 SkipTake必须使用 OrderBy,否则每次返回的顺序可能不一致。

    • 性能优化:对于SQL查询分页,确保排序字段已建立索引

    • 参数验证:始终验证 PageNumberPageSize,防止恶意请求过大页码或页尺寸。

    • 连接池:确保数据库连接字符串配置了合适的连接池。

💎 总结

选择合适的分页方法主要取决于你的数据量性能要求

  • 对于大多数需要良好性能和用户体验的场景,推荐使用 SQL查询分页Skip/Take)。

  • 仅在数据量很小且稳定,且开发速度优先时才考虑内存分页

  • 使用 PagedResult<T> 这样的通用类可以帮助你规范分页数据的返回格式。

  • 对于复杂场景,可以考虑使用像 X.PagedList 这样成熟的第三方库。

希望这些信息能帮助你在ASP.NET Core项目中有效地实现分页功能。如果你在实现过程中遇到任何具体问题,欢迎随时追问。

创建分页参数模型 在模型中,我们通常需要知道当前是第几页以及每页显示多少条记录。 csharp public class PaginationParams { private const int MaxPageSize = 50; // 防止请求过多数据 private int _pageSize = 10; // 默认每页10条 public int PageNumber { get; set; } = 1; // 当前页码,默认第1页 public int PageSize { get => _pageSize; set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value; } } 修改控制器与Action 在控制器中,我们接收分页参数,并传递给服务层或数据访问层。 csharp [HttpGet] public async Task GetUsers(PaginationParams paginationParams) { // 将分页参数传递给服务方法,获取分页后的数据 var pagedUsers = await _userService.GetPagedUsersAsync(paginationParams); return Ok(pagedUsers); } 服务层或数据访问层处理分页逻辑 这里以Entity Framework Core为例,使用 Skip 和 Take 方法生成分页查询。 csharp public async Task> GetPagedUsersAsync(PaginationParams paginationParams) { var query = _context.Users.AsNoTracking(); // AsNoTracking提升查询性能 // 先获取总记录数,用于计算总页数 var totalCount = await query.CountAsync(); // 然后获取当前页的数据 var items = await query .OrderBy(u => u.Id) // 必须指定排序 .Skip((paginationParams.PageNumber - 1) * paginationParams.PageSize) .Take(paginationParams.PageSize) .ToListAsync(); return new PagedResult(items, totalCount, paginationParams.PageNumber, paginationParams.PageSize); } 返回分页结果 定义一个通用的分页结果类,方便前端使用。 csharp public class PagedResult { public List Items { get; set; } // 当前页的数据列表 public int TotalCount { get; set; } // 总记录数 public int PageNumber { get; set; } // 当前页码 public int PageSize { get; set; } // 每页记录数 public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); // 计算总页数 public bool HasPrevious => PageNumber > 1; public bool HasNext => PageNumber < TotalPages; public PagedResult(List items, int totalCount, int pageNumber, int pageSize) { Items = items; TotalCount = totalCount; PageNumber = pageNumber; PageSize = pageSize; } } 使用内存分页 如果你的数据量确实很小,并且确定要一次性加载,可以使用 PagedDataSource 进行分页,但请注意其性能局限。 csharp // 注意:此方法需一次性加载所有数据,请谨慎用于大数据量场景:cite[2] public async Task> GetPagedUsersInMemory(PaginationParams paginationParams) { var allUsers = await _context.Users.ToListAsync(); // 获取所有数据 var pagedDataSource = new PagedDataSource { DataSource = allUsers, AllowPaging = true, CurrentPageIndex = paginationParams.PageNumber - 1, PageSize = paginationParams.PageSize }; return new PagedResult( pagedDataSource.Cast().ToList(), allUsers.Count, paginationParams.PageNumber, paginationParams.PageSize ); } 📄 前端显示与分页导航 获取到分页数据后,你需要在前端显示分页导航。一种常见做法是将分页信息放在响应Header中(如X-Pagination),前端JavaScript解析后生成分页按钮。 这里以直接在视图中(Razor Pages 或 MVC)生成为例: html @model PagedResult

@foreach (var user in Model.Items) { }

@user.Name

💡 进阶分页辅助类与最佳实践 考虑使用第三方分页库 像 X.PagedList 这样的库可以简化很多工作。你可以通过NuGet安装 X.PagedList.Mvc.Core。 csharp public async Task> GetPagedListAsync(int pageNumber, int pageSize) { return await _context.Users.ToPagedListAsync(pageNumber, pageSize); } 数据库特定分页语法 对于超大规模数据,深入研究数据库提供的分页语法很有帮助。例如,SQL Server 可使用 ROW_NUMBER() 或 OFFSET-FETCH;Oracle 可使用 ROWNUM 或 ROW_NUMBER()。 最佳实践与注意事项 必须指定排序:使用 Skip 和 Take 前必须使用 OrderBy,否则每次返回的顺序可能不一致。 性能优化:对于SQL查询分页,确保排序字段已建立索引。 参数验证:始终验证 PageNumber 和 PageSize,防止恶意请求过大页码或页尺寸。 连接池:确保数据库连接字符串配置了合适的连接池。 💎 总结 选择合适的分页方法主要取决于你的数据量和性能要求: 对于大多数需要良好性能和用户体验的场景,推荐使用 SQL查询分页(Skip/Take)。 仅在数据量很小且稳定,且开发速度优先时才考虑内存分页。 使用 PagedResult 这样的通用类可以帮助你规范分页数据的返回格式。 对于复杂场景,可以考虑使用像 X.PagedList 这样成熟的第三方库。 希望这些信息能帮助你在ASP.NET Core项目中有效地实现分页功能。如果你在实现过程中遇到任何具体问题,欢迎随时追问。