本文屬于OData系列
目錄
- 武裝你的WEBAPI-OData入門
- 武裝你的WEBAPI-OData便捷查詢
- 武裝你的WEBAPI-OData分頁查詢
- 武裝你的WEBAPI-OData資源更新Delta
- 武裝你的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查詢全部搞定,梭哈!不要問就是梭!
參考資料
- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/965a7cb1-663e-4eef-b9f6-388c7e5c9444?redirectedfrom=MSDN
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/30442.html
標籤:.NET Core
上一篇:微服務框架 ketchup 介紹
