本文屬于OData系列
目錄
- 武裝你的WEBAPI-OData入門
- 武裝你的WEBAPI-OData便捷查詢
- 武裝你的WEBAPI-OData分頁查詢
- 武裝你的WEBAPI-OData資源更新Delta
- 武裝你的WEBAPI-OData之EDM
- 武裝你的WEBAPI-OData常見問題
- 武裝你的WEBAPI-OData使用Endpoint
Introduction
前文大概介紹了下OData,本文介紹下它強大便捷的查詢,(后面的介紹都基于最新的OData V4)
假設現在有這么兩個物體類,并按照前文建立了OData配置,
public class DeviceInfo
{
[Key]
[MaxLength(200)]
public string DeviceId { get; set; }
//....//
public float Longitude { get; set; }
public Config[] Layout { get; set; }
}
public class AlarmInfo
{
[Key]
[MaxLength(200)]
public string Id { get; set; }
public string DeviceId { get; set; }
public string Type { get; set; }
[ForeignKey("DeviceId")]
public virtual DeviceInfo DeviceInfo { get; set; }
public bool Handled { get; set; }
public long Timestamp { get; set; }
}
Controller設定如下,很簡單,核心就幾行:
[ODataRoute]
[EnableQuery]
[ProducesResponseType(typeof(ODataValue<IEnumerable<DeviceInfo>>), Status200OK)]
public IActionResult Get()
{
return Ok(_context.DeviceInfoes.AsQueryable());
}
[ODataRoute("({key})")]
[EnableQuery]
[ProducesResponseType(typeof(ODataValue<IDeviceInfo>), Status200OK)]
public IActionResult GetSingle([FromODataUri] string key)
{
var result = _context.DeviceInfoes.Find(key);
if (result == null) return NotFound(new ODataError() { ErrorCode = "404", Message = "Cannnot find key." });
return Ok(result);
}
集合與單物件
使用OData,可以很簡單的訪問集合與單個物件,
//訪問集合
GET http://localhost:9000/api/DeviceInfoes
//訪問單個物件,注意string型別需要用單引號,
GET http://localhost:9000/api/DeviceInfoes('device123')
$Filter
請求集合很多,我們需要使用到查詢來篩選資料,注意,這個查詢是在服務器端執行的,
//查詢特定設備在一個時間的資料,注意這里需要手動處理一下這個ID為deviceid,
GET http://localhost:9000/api/AlarmInfoes('device123')?$filter=(TimeStamp ge 12421552) and (TimeStamp le 31562346785561)
$Filter支持很多種語法,可以讓資料篩選按照呼叫方的意圖進行自由組合,極大地提升了API的靈活性,
$Expand
在查詢alarminfo的時候,我很需要API能夠回傳所有資訊,其中包括對應的deviceinfo資訊,但是標準的回傳是這樣的:
{
"@odata.context": "http://localhost:9000/api/$metadata#AlarmInfoes",
"value": [
{
"id": "235314",
"deviceId": "123",
"type": "低溫",
"handled": true,
"timestamp": 1589235890047
},
{
"id": "6d2d3af3-2cf7-447e-822f-aab4634b3a13",
"deviceId": "5s3",
"type": null,
"handled": true,
"timestamp": 0
}]
}
只有一個干巴巴的deviceid,要實作展示所有資訊的功能,就只能再從deviceinfo的API請求一次,獲取對應ID的devceinfo資訊,
這就是所謂的N+1問題:請求具有子集的串列時,需要請求1次集合+請求N個集合內資料的具體內容,會造成查詢效率的降低,
不爽是不是?看下OData怎么做,請求如下:
GET http://localhost:9000/api/alarminfoes?$expand=deviceinfo
通過指定expand引數,就可以在回傳中追加導航屬性(navigation property)的資訊,
本例中是使用的外鍵實作的,
{
"@odata.context": "http://localhost:9000/api/$metadata#AlarmInfoes(deviceInfo())",
"value": [
{
"id": "235314",
"deviceId": "123",
"type": "低溫",
"handled": true,
"timestamp": 1589235890047,
"deviceInfo": {
"deviceId": "123",
"name": null,
"deviceType": null,
"location": "plex",
"description": "99",
"longitude": 99,
"layout": []
}
}
層級查詢
實作了展開之后,那能否查詢在多個層級內的資料呢?也可以,考慮查詢所有longitude大于90的alarmtype,
GET http://localhost:9000/api/alarminfoes?$expand=deviceinfo&$filter=deviceinfo/longitude gt 90
注意,這里表示層級不是使用
.而是使用的/,如果使用.容易被瀏覽器誤識別,需要特別配置一下,
結果如下:
{
"@odata.context": "http://localhost:9000/api/$metadata#AlarmInfoes(deviceInfo())",
"value": [
{
"id": "235314",
"deviceId": "123",
"type": "低溫",
"handled": true,
"timestamp": 1589235890047,
"deviceInfo": {
"deviceId": "123",
"name": null,
"deviceType": null,
"location": "plex",
"description": "99",
"longitude": 99,
"layout": []
}
}
any/all
可以使用any/all來執行對集合的判斷,
這個參考:
GET http://services.odata.org/V4/TripPinService/People?$filter=Emails/any(e: endswith(e, 'contoso.com'))
對于我的例子,我使用
GET http://localhost:9000/api/DeviceInfoes?$filter=layout/any(e: e/description eq '0')
服務器會彈出500服務器錯誤,提示:
System.InvalidOperationException: The LINQ expression 'DbSet<DeviceInfo>
.Where(d => d.Layout
.Any(e => e.Description.Contains(__TypedProperty_0)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
查看檔案之后,原來是我資料的層級比較深了,不在頂級層級型別的查詢,在EF Core3.0的版本之前是支持的,在3.0以后就不支持了,主要是怕服務器記憶體溢位,如果真的要支持,那么就使用AsEnumerable()去實作客戶端查詢,
datetime相關
有同學已經注意到了,我這邊使用的timestamp的格式是unix形式的時間戳,而沒有使用到js常用的ISO8601格式(2004-05-03T17:30:08+08:00),如果需要使用到這種時間怎么辦?
OData提供了一個語法,使用(datetime'2016-10-01T00:00:00')這種形式來表示Datetime,這樣就能夠實作基于datetime的查詢,
查詢順序
謂詞會使用以下的順序:$filter, $inlinecount(在V4中已經替換為$count), $orderby, $skiptoken, $skip, $top, $expand, $select, $format,
filter總時最優先的,隨后就是count,因此查詢回傳的count總是符合要求的所有資料的計數,
范例學習
官方有一個查詢的Service,可以參考學習和試用,給了很多POSTMAN的示例,
不過有一些略坑的是,有的內容比如Paging里面,加入$count的回傳是錯的,
前端指南
查詢的方法這么復雜,如果手動去拼接query string還是有點虛的,好在有很多庫可以幫助我們做這個,
官方提供了各種語言的Client和Server的庫:https://www.odata.org/libraries/
前端比較常用的有o.js
const response = await o('http://my.url')
.get('User')
.query({$filter: `UserName eq 'foobar'`});
console.log(response); // If one -> the exact user, otherwise an array of users
這樣可以免去拼請求字串的煩惱,關于o.js的詳細介紹,可以看這里,
參考資料
- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/965a7cb1-663e-4eef-b9f6-388c7e5c9444?redirectedfrom=MSDN
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/30400.html
標籤:.NET Core
