本文屬於OData系列javascript
目錄(可能會有後續修改)html
- 武裝你的WEBAPI-OData入門
- 武裝你的WEBAPI-OData便捷查詢
- 武裝你的WEBAPI-OData分頁查詢
- 武裝你的WEBAPI-OData資源更新
- 武裝你的WEBAPI-OData之EDM
- 武裝你的WEBAPI-OData格式轉換
- 武裝你的WEBAPI-OData使用Endpoint
前文大概介紹了下OData,本文介紹下它強大便捷的查詢。(後面的介紹都基於最新的OData V4)前端
假設如今有這麼兩個實體類,並按照前文創建了OData配置。java
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設置以下,很簡單,核心就幾行:git
[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,能夠很簡單的訪問集合與單個對象。express
//訪問集合 GET http://localhost:9000/api/DeviceInfoes //訪問單個對象,注意string類型須要用單引號。 GET http://localhost:9000/api/DeviceInfoes('device123')
請求集合不少,咱們須要使用到查詢來篩選數據,注意,這個查詢是在服務器端執行的。json
//查詢特定設備在一個時間的數據,注意這裏須要手動處理一下這個ID爲deviceid。 GET http://localhost:9000/api/AlarmInfoes('device123')?$filter=(TimeStamp ge 12421552) and (TimeStamp le 31562346785561)
$Filter支持不少種語法,可讓數據篩選按照調用方的意圖進行自由組合,極大地提高了API的靈活性。c#
在查詢alarminfo的時候,我很須要API可以返回全部信息,其中包括對應的deviceinfo信息。可是標準的返回是這樣的:windows
{ "@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信息。api
這就是所謂的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來執行對集合的判斷。
這個參考:
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()
去實現客戶端查詢。
有同窗已經注意到了,我這邊使用的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
的詳細介紹,能夠看這裏。