OData武装你的WEBAPI-分页查询
本文属于OData系列
目录
- 武装你的WEBAPI-OData入门
- 武装你的WEBAPI-OData便捷查询
- 武装你的WEBAPI-OData分页查询
- 武装你的WEBAPI-OData资源更新
- 武装你的WEBAPI-OData之EDM
- 武装你的WEBAPI-OData常见问题
- 武装你的WEBAPI-OData使用Endpoint
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三个关键字。
- 默认情况,服务器返回所有的记录。
- 假设按照每页10条记录进行分页,那么我们首次请求(请求第一页)应该使用
$count=true&$skip=0&$top=10
获取第一页数据,同时带有数据计数。 - 根据第一次请求获得数据计数,可以快速计算总共的分页数量。比如返回count=72,那么总共的页数应该是72/10 + 1 =8页(最后一页只有2个数据)
- 生成每个页码的链接,第二页应该是
$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-分页查询的更多相关文章
- 武装你的WEBAPI-OData便捷查询
本文属于OData系列 目录(可能会有后续修改) 武装你的WEBAPI-OData入门 武装你的WEBAPI-OData便捷查询 武装你的WEBAPI-OData分页查询 武装你的WEBAPI-ODa ...
- 让Asp.net mvc WebAPI 支持OData协议进行分页查询操作
这是我在用Asp.net mvc WebAPI 支持 OData协议 做分页查询服务时的 个人拙笔. 代码已经开发到oschina上.有兴趣的朋友可以看看,欢迎大家指出不足之处. 看过了园子里的几篇关 ...
- AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)
AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI OData 协议下,查询分页.或者是说 本人在使用Asp.Net webAPI 做服务接口时写的一个分页 ...
- AspNet.WebAPI.OData.ODataPQ实现WebAPI的分页查询服务-(个人拙笔)(转)
出处:http://www.bubuko.com/infodetail-827612.html AspNet.WebAPI.OData.ODataPQ 这是针对 Asp.net WebAPI ODat ...
- 让OData和NHibernate结合进行动态查询
OData是一个非常灵活的RESTful API,如果要做出强大的查询API,那么OData就强烈推荐了.http://www.odata.org/ OData的特点就是可以根据传入参数动态生成Ent ...
- .net通用CMS快速开发框架——问题1:Dapper通用的多表联合分页查询怎么破?
最近在弄一个东东,类似那种CMS的后台管理系统,方便作为其它项目的初始化框架用的. 现在遇到个问题,如标题所示:Dapper通用的多表联合分页查询怎么破? 难道只能通过拼接sql或者使用存储过程吗?我 ...
- C# WebAPI分页实现分享
第一次分享代码,不足或不对之处请指正.. 需求:微信端传递不同的参数调用WebAPI进行分页查询菜谱计划点评结果 思路:基于视图来查询,根据传递的不同参数拼接分页查询Sql来查询. 分页的sql如下 ...
- ASPNETCOREAPI 跨域处理 SQL 语句拼接 多条件分页查询 ASPNET CORE 核心 通过依赖注入(注入服务)
ASPNETCOREAPI 跨域处理 AspNetCoreApi 跨域处理 如果咱们有处理过MV5 跨域问题这个问题也不大. (1)为什么会出现跨域问题: 浏览器安全限制了前端脚本跨站点的访问资源, ...
- JdbcTemplate+PageImpl实现多表分页查询
一.基础实体 @MappedSuperclass public abstract class AbsIdEntity implements Serializable { private static ...
随机推荐
- Python - 生成随机验证码的3种实现方式
生成6位随机验证码的3种实现方式如下: 1. 简单粗暴型:所有数字和字母都放入字符串: 2. 利用ascii编码的规律,遍历获取字符串和数字的字符串格式: 3. 引用string库. 方法1代码: i ...
- ubuntu server 18.04 网络配置
从17.10开始放弃在/etc/network/interfaces里固定IP的配置 配置文件是:/etc/netplan/50-cloud-init.yaml .用缩进来表示层级关系 冒号之后要有个 ...
- JVM原理与深度调优(一)
什么是jvm jvm是java虚拟机 运行在用户态.通过应用程序实现java代码跨平台.与平台无关.实际上是"一次编译,到处执行" 1.从微观来说编译出来的是字节码!去到哪个平台都 ...
- k8s namespace限制调研
1.创建namespace gpu 2.增加限制 [root@tensorflow1 gpu-namespace]# cat compute-resources.yaml apiVersion: v1 ...
- element-ui中cascader同时获取label和value值
关于elementUI中cascader选中值后,能获取value或者label,但不能同时获value和label,这一问题,琢磨出了这么个办法.以新增和编辑城市为例,type: 1 编辑,type ...
- mpvue开发微信小程序之时间+日期选择器
最近在做微信小程序,技术栈为mpvue+iview weapp组件库. 因项目需求,要用到日期+时间选择器,iview组件库目前还未提供时间日期选择器的组件,小程序官方组件日期时间也是分开的,在简书上 ...
- JQuery学习(一)
本文是学习廖老师的Javascript全栈教程后的一些笔记. 使用jQuery: 方法一:下载jQuery库,并在html页面中引入,方式如下: 1 <html> 2 <head&g ...
- 怎么查看当前的git分支是基于哪个分支创建的?
2019独角兽企业重金招聘Python工程师标准>>> Question: 比如从 branch A 切出一个 branch B 然后对branch B做了一系列的操作 然后忘记了b ...
- 信息竞赛进阶指南--递归法求中缀表达式的值,O(n^2)(模板)
// 递归法求中缀表达式的值,O(n^2) int calc(int l, int r) { // 寻找未被任何括号包含的最后一个加减号 for (int i = r, j = 0; i >= ...
- MySQL Linux 环境安装
1.卸载 MySQL #查看 Linux 中是否有 mysql rpm -aq |grep -i mysql #有的话通过下面命令卸载删除 rpm -ev mysql-libs-**** --node ...