本文属于OData系列

目录


Introduction

分页是数据请求避免不了的问题,数据很多的情况下,通过GET请求一次性返回所有的数据,不光性能底下,而且不好展示。

分页的原理就是客户端请求服务器,服务器返回的数据是有限的数据(限制于pageSize),同时返回一个数据的总量count,方便客户端进行处理。也有另外一种实现,使用nextlink指示下一页的位置。

传统实现

传统的实现,我比较喜欢LINQ的Skip和Take方法。

/// <summary>
/// 有参GET请求
/// </summary>
/// <returns></returns>
[HttpGet("page")]
[ProducesResponseType(typeof(ReturnData<Page<UserInfoModel>>), Status200OK)]
[ProducesResponseType(typeof(ReturnData<string>), Status404NotFound)]
public async Task<ActionResult> Get(string username, int pageNo, int pageSize)
{
if (pageSize <= 0 || pageNo <= 0) return BadRequest(new ReturnData<string>("Error request"));
IEnumerable<UserInfoModel> result;
if (string.IsNullOrWhiteSpace(username))
result = _userManager.Users.Select(w => ToUserInfoModel(w)).ToList();
else
result = _userManager.Users.Select(w => ToUserInfoModel(w)).ToList().Where(w => w.Username.Contains(username));
var response = result.Skip((pageNo - 1) * pageSize).Take(pageSize);
Page<UserInfoModel> page = new Page<UserInfoModel>() { PageNo = pageNo, PageSize = pageSize, Result = response, TotalCount = result.Count() };
return Ok(new ReturnData<Page<UserInfoModel>>(page));
}

通过传递username、pageNo和pageSize即可实现分页功能。

OData实现分页

OData查询不需要后端再自行设计接受参数、实现等内容,并且支持两种方式实现分页:客户端模式和服务器模式。首先我们需要补补几个关键字的用法:(适用于OData V4)

$count

count关键字可以随同查询一起使用,使用$count=true的形式即可在查询结果中追加返回符合查询条件的所有的记录的数量。

GET http://localhost:9000/api/devicedatas('ZW000001')?$count=true

注意这里不是返回的当前结果的计数。

{
"@odata.context": "http://localhost:9000/api/$metadata#DeviceDatas",
"@odata.count": 80,
"value": [
{
"id": "554b1ed8-6429-4ad3-83f9-45c7696547e6",
"deviceId": "ZW000001",
"timestamp": 1589544960000,
"dataArray": []
},
...

$skip

skip关键字可以指定跳过的记录数量,使用$skip=10这种形式。

GET http://localhost:9000/api/devicedatas('ZW000001')?$skip=30

返回的结果是跳过了前面的N条记录。

$top

top关键字指定截取的符合查询条件中的前n条记录,使用top=10这种形式。

GET http://localhost:9000/api/devicedatas('ZW000001')?$top=10

$skiptoken

skiptoken这个东西和前面的东西都不一样。skiptoken必须要服务器返回,一般来说是服务器根据主键的形式返回结果,然后调用方直接调用。经常出现在nextlink中,用于服务器分页。

GET http://localhost:9000/api/devicedatas('ZW000001')?$skiptoken='554b1ed8-6429-4ad3-83f9-45c7696547e6'

注意这里不是返回的当前结果的计数。

{
"@odata.context": "http://localhost:9000/api/$metadata#DeviceDatas",
"value": [
{
"id": "554b1ed8-6429-4ad3-83f9-45c7696547e6",
"deviceId": "ZW000001",
"timestamp": 1589544960000,
"dataArray": []
},
...

客户端模式

客户端模式是客户端主导的分页实现,分页的页数数量之类的,都需要由客户端指定,对客户端来说,比较灵活。主要使用到count、skip和top三个关键字。

  1. 默认情况,服务器返回所有的记录。
  2. 假设按照每页10条记录进行分页,那么我们首次请求(请求第一页)应该使用$count=true&$skip=0&$top=10获取第一页数据,同时带有数据计数。
  3. 根据第一次请求获得数据计数,可以快速计算总共的分页数量。比如返回count=72,那么总共的页数应该是72/10 + 1 =8页(最后一页只有2个数据)
  4. 生成每个页码的链接,第二页应该是$count=true&$skip=10&$top=10
GET http://localhost:9000/api/devicedatas('ZW000001')?$count=true&$skip=10&$top=10
  • 这几条命令需要先启用,可以在startup.cs中修改:
app.UseMvc(
routeBuilder =>
{
// the following will not work as expected
// BUG: https://github.com/OData/WebApi/issues/1837
// routeBuilder.SetDefaultODataOptions( new ODataOptions() { UrlKeyDelimiter = Parentheses } );
routeBuilder.ServiceProvider.GetRequiredService<ODataOptions>().UrlKeyDelimiter = Parentheses; // global odata query options
//routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(600).Count().SkipToken(); routeBuilder.MapVersionedODataRoutes("odata", "api", modelBuilder.GetEdmModels());
});

服务端模式

客户端模式灵活,但是有一个问题不好处理:客户端在两次请求的过程中,数据发生了变化,那会遇到一些意想不到的问题,比如说数据删除了其中的一些,那么某条数据很有可能会同时出现在两个页。因此,可以让服务器帮我们做分页,服务器管理所有的数据,对两次请求的数据变化也能及时感知,不会出现这个问题。

服务端模式需要使用到skiptoken和pagesize设置。

服务端模式,客户端请求集合,服务器返回部分数据,同时提供一个nextlink,客户端直接请求这个链接,就可以获得更多的数据。

skiptoken启用可以参考上面客户端模式的代码。pagesize是服务器最多每页返回多少条数据的设置,可以在上面全局指定,也可以在具体的方法上面指定。

[ODataRoute]
[EnableQuery(PageSize = 1)]
[ProducesResponseType(typeof(ODataValue<IEnumerable<DeviceInfo>>), Status200OK)]
public IActionResult Get()
{
return Ok(_context.DeviceInfoes.AsQueryable());
}

试着使用原始的方式进行请求。

GET http://localhost:9000/api/DeviceInfoes?$count=true

返回结果如下,能看到,返回的数据的结尾,多了一个@odata.nextLink,这个直接点击,就可以直接请求下一组数据。在下一组数据中又会有在下一组数据的地址,直到最后一组数据。

{
"@odata.context": "http://localhost:9000/api/$metadata#DeviceInfoes",
"@odata.count": 3,
"value": [
{
"deviceId": "ZW000001",
"name": null,
"deviceType": null,
"imagePath": null,
"layout": []
}
],
"@odata.nextLink": "http://localhost:9000/api/DeviceInfoes?$count=true&$skiptoken=deviceId-'ZW000001'"
}

注意:

  • 我这里主键使用的是字符串类型,并且用的是EF CORE 3.0,直接请求会返回服务器错误,需要自行指定string的比较模式,可以使用AsEnumerable()在System.Linq中处理。如果使用的主键是数值型,那么应该不会有这个问题。参考这里
  • 可以在请求中同时应用skip等客户端模式的语法,构造自己需要的数据。

看完服务器模式,感觉这模式有点僵硬啊,只能一条一条地获取下一个链接,我要直接跳几页的时候怎么办呢?

首先你需要了解分页的模式,我们请求http://services.odata.org/V4/TripPinService/People返回的nextlink会是这样子的:

"@odata.nextLink": "https://services.odata.org/V4/TripPinService/People?%24skiptoken=8"

我这里使用到了官方提供的一个地址,返回了8条数据,同时指示了下一个链接的位置,很明显,这个skiptoken=8是从第9个开始的,因此指定的只是一个开头的地址,我们可以自行修改成其他数字。(前面说到skiptoken必须要服务生成,指的是后面的查询模式需要是由服务器生成。)

那么对于第三页就是skiptoken=16。但是由于服务器指定了分页的大小8,我们查询还是不方便,可以通过继承EnableQueryAttribute实现,将这个[MyEnableQueryAttribute]替代刚刚的[EnableQuery]。搬运

public class MyEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
int pagesize = xxx;
var result = queryOptions.ApplyTo(queryable, new ODataQuerySettings { PageSize = pagesize });
return result;
}
}

总结

OData使用客户端模式的分页和服务端的分页都能够很方便地实现分页查询。一个GET查询全部搞定,梭哈!不要问就是梭!

参考资料

OData武装你的WEBAPI-分页查询的更多相关文章

  1. 武装你的WEBAPI-OData便捷查询

    本文属于OData系列 目录(可能会有后续修改) 武装你的WEBAPI-OData入门 武装你的WEBAPI-OData便捷查询 武装你的WEBAPI-OData分页查询 武装你的WEBAPI-ODa ...

  2. 让Asp.net mvc WebAPI 支持OData协议进行分页查询操作

    这是我在用Asp.net mvc WebAPI 支持 OData协议 做分页查询服务时的 个人拙笔. 代码已经开发到oschina上.有兴趣的朋友可以看看,欢迎大家指出不足之处. 看过了园子里的几篇关 ...

  3. AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)

    AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI OData 协议下,查询分页.或者是说 本人在使用Asp.Net webAPI 做服务接口时写的一个分页 ...

  4. AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)(转)

    出处:http://www.bubuko.com/infodetail-827612.html AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI ODat ...

  5. 让OData和NHibernate结合进行动态查询

    OData是一个非常灵活的RESTful API,如果要做出强大的查询API,那么OData就强烈推荐了.http://www.odata.org/ OData的特点就是可以根据传入参数动态生成Ent ...

  6. .net通用CMS快速开发框架——问题1:Dapper通用的多表联合分页查询怎么破?

    最近在弄一个东东,类似那种CMS的后台管理系统,方便作为其它项目的初始化框架用的. 现在遇到个问题,如标题所示:Dapper通用的多表联合分页查询怎么破? 难道只能通过拼接sql或者使用存储过程吗?我 ...

  7. C# WebAPI分页实现分享

    第一次分享代码,不足或不对之处请指正.. 需求:微信端传递不同的参数调用WebAPI进行分页查询菜谱计划点评结果 思路:基于视图来查询,根据传递的不同参数拼接分页查询Sql来查询. 分页的sql如下 ...

  8. ASPNETCOREAPI 跨域处理 SQL 语句拼接 多条件分页查询 ASPNET CORE 核心 通过依赖注入(注入服务)

    ASPNETCOREAPI 跨域处理 AspNetCoreApi 跨域处理 如果咱们有处理过MV5 跨域问题这个问题也不大. (1)为什么会出现跨域问题:  浏览器安全限制了前端脚本跨站点的访问资源, ...

  9. JdbcTemplate+PageImpl实现多表分页查询

    一.基础实体 @MappedSuperclass public abstract class AbsIdEntity implements Serializable { private static ...

随机推荐

  1. kafka相关术语名词

    Topic:标签名,一个消息队列的名称 Producer:生产者,发布消息 Consumer:消费者,订阅发布消息,进行处理的存在 Broker:kafka集群,有一个.多个Topic Partiti ...

  2. 新的知识点来了-ES6 Proxy代理 和 去银行存款有什么关系?

    ES给开发者提供了一个新特性:Proxy,就是代理的意思.也就是我们这一节要介绍的知识点. 以前,ATM还没有那么流行的时候(暴露年纪),我们去银行存款或者取款的时候,需要在柜台前排队,等柜台工作人员 ...

  3. Android 中 OkHttp 三步实现生命周期绑定

    简介 OkHttps 是 OkHttp 增强版的超轻量封装包. 和 Retrofit 相比,它更加轻量(只有 59Kb),是 Retrofit (124Kb)的一半,而且更加的开箱即用,API 更加自 ...

  4. Java 基础之详解 Java IO

    Java IO基本概念 Java IO:即Java输入/输出系统,区分Java的输入和输出:把自己当成程序, 当你从外边读数据到自己这里就用输入(InputStream/Reader), 向外边写数据 ...

  5. Apache多处理模块

    介绍 Apache HTTP 服务器被设计为一个功能强大,并且灵活的 web 服务器, 可以在很多平台与环境中工作.不同平台和不同的环境往往需要不同 的特性,或可能以不同的方式实现相同的特性最有效率. ...

  6. Spring Boot JPA中关联表的使用

    文章目录 添加依赖 构建Entity 构建Repository 构建初始数据 测试 Spring Boot JPA中关联表的使用 本文中,我们会将会通过一个Book和Category的关联关系,来讲解 ...

  7. js 函数对象的继承 inherit 带 插件完整解析版[helpers.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS的对象继承方法 需求分析: 1. ...

  8. 基于JSR-356实现的Tyrus WebSocket框架的消息传递机制初步了解

    对阻塞.非阻塞,同步.异步初步了解了,不是太明白,其中大多数将的是对于CPU的使用率及对一个事件的处理效率. 阻塞与非阻塞各有优缺点,在知乎上也看到了下面的结论: 在处理 IO 的时候,阻塞和非阻塞都 ...

  9. Hadoop学习笔记(三) ——HDFS

    参考书籍:<Hadoop实战>第二版 第9章:HDFS详解 1. HDFS基本操作 @ 出现的bug信息 @-@ WARN util.NativeCodeLoader: Unable to ...

  10. 数学--数论--随机算法--Pollard Rho 大数分解算法(纯模板带输出)

    ACM常用模板合集 #include <bits/stdc++.h> using namespace std; typedef long long ll; ll pr; ll pmod(l ...