[Web API][OData][筆記] OData初體驗

小喵在一次去 TWMVC 的場合上課中,聽到 KKBruce 講解有關Web API的內容,這裡面提到了OData這個東西,感覺還蠻有趣的,後來 KKBruce 大大也來小喵的部落格留言,也提到了 OData 。小喵開始找一下相關的資料,發現 OData 搭配 WebAPI 有蠻多不錯的運用。於是,就來場小喵與OData的初體驗吧~

緣起

小喵在一次去 TWMVC 的場合上課中,聽到 KKBruce 講解有關Web API的內容,這裡面提到了OData這個東西,感覺還蠻有趣的,後來 KKBruce 大大也來小喵的部落格留言,也提到了 OData 。小喵開始找一下相關的資料,發現 OData 搭配 WebAPI 有蠻多不錯的運用。於是,就來場小喵與OData的初體驗吧~

 

準備

首先小喵先把基本測試測試的東西建立起來,資料使用北風資料庫中的 Products 來進行測試,暫時先不透過 Entity Framework 或者工具建立物件,暫時以手工打造的方式來做體驗。

北風Product的類別,小喵就暫時先在Model中,新增了ProductInfo的類別

Public Class ProductInfo
    Private m_ProductID As Decimal
    Private m_ProductName As String = ""
    Private m_SupplierID As Decimal
    Private m_CategoryID As Decimal
    Private m_QuantityPerUnit As String = ""
    Private m_UnitPrice As Decimal
    Private m_UnitsInStock As Decimal
    Private m_UnitsOnOrder As Decimal
    Private m_ReorderLevel As Decimal
    Private m_Discontinued As Boolean = False

    Public Property ProductID As Decimal
        Get
            Return m_ProductID
        End Get
        Set(value As Decimal)
            m_ProductID = value
        End Set
    End Property

    Public Property ProductName As String
        Get
            Return m_ProductName
        End Get
        Set(value As String)
            m_ProductName = value
        End Set
    End Property

    Public Property SupplierID As Decimal
        Get
            Return m_SupplierID
        End Get
        Set(value As Decimal)
            m_SupplierID = value
        End Set
    End Property

    Public Property CategoryID As Decimal
        Get
            Return m_CategoryID
        End Get
        Set(value As Decimal)
            m_CategoryID = value
        End Set
    End Property

    Public Property QuantityPerUnit As String
        Get
            Return m_QuantityPerUnit
        End Get
        Set(value As String)
            m_QuantityPerUnit = value
        End Set
    End Property

    Public Property UnitPrice As Decimal
        Get
            Return m_UnitPrice
        End Get
        Set(value As Decimal)
            m_UnitPrice = value
        End Set
    End Property

    Public Property UnitsInStock As Decimal
        Get
            Return m_UnitsInStock
        End Get
        Set(value As Decimal)
            m_UnitsInStock = value
        End Set
    End Property

    Public Property UnitsOnOrder As Decimal
        Get
            Return m_UnitsOnOrder
        End Get
        Set(value As Decimal)
            m_UnitsOnOrder = value
        End Set
    End Property

    Public Property ReorderLevel As Decimal
        Get
            Return m_ReorderLevel
        End Get
        Set(value As Decimal)
            m_ReorderLevel = value
        End Set
    End Property

    Public Property Discontinued As Boolean
        Get
            Return m_Discontinued
        End Get
        Set(value As Boolean)
            m_Discontinued = value
        End Set
    End Property

End Class

 

另外寫個 ProductDAO ,用來寫資料存取的部分,先寫取得全部資料的一個 Public Function 用來讀出所有的資料,並且傳回List (Of ProductInfo)

    Public Function GetAllProducts() As List(Of ProductInfo)
        Try
            Dim oConnStr As New ConnStrInfo
            Dim ConnStr As String = oConnStr.ConnStr
            Using Conn As New SqlConnection(ConnStr)
                Conn.Open()
                Dim oProds As New List(Of ProductInfo)
                Dim tProd As ProductInfo

                Dim SqlTxt As String = ""
                SqlTxt &= " SELECT ProductID "
                SqlTxt &= " FROM Products (NOLOCK) "
                SqlTxt &= "  "
                Using Cmmd As New SqlCommand(SqlTxt, Conn)
                    Dim Dr As SqlDataReader = Cmmd.ExecuteReader
                    If Dr.HasRows Then
                        While Dr.Read
                            tProd = New ProductInfo
                            tProd.ProductID = Dr.Item("ProductID")
                            tProd.ProductName = Dr.Item("ProductName")
                            tProd.SupplierID = Dr.Item("SupplierID")
                            tProd.CategoryID = Dr.Item("CategoryID")
                            tProd.QuantityPerUnit = Dr.Item("QuantityPerUnit")
                            tProd.UnitPrice = Dr.Item("UnitPrice")
                            tProd.UnitsInStock = Dr.Item("UnitsInStock")
                            tProd.UnitsOnOrder = Dr.Item("UnitsOnOrder")
                            tProd.ReorderLevel = Dr.Item("ReorderLevel")
                            tProd.Discontinued = Dr.Item("Discontinued")

                            oProds.Add(tProd)
                        End While
                    End If
                    Dr.Close()
                End Using
                Return oProds

            End Using
        Catch ex As Exception
            Throw New Exception(ex.Message)
        End Try
    End Function

最後,再準備可以傳回全部 Products 的 Controller

' GET api/products
Public Function GetValues() As List(Of ProductInfo)
    'Return New String() {"value1", "value2"}
    Dim ProdObj As New ProductDAO
    Dim oProds As List(Of ProductInfo) = ProdObj.GetAllProducts()
    Return oProds
End Function

這樣準備好,就可以開始測試看看,可以傳回全部的Products的資料了

 

OData 變身~

要將本來輸出為List(Of Object)轉換為輸出可支援OData的內容,其實只要小小的三個步驟

  1. 修飾本來的Function,設定他變成可以查詢【 Queryable 】
  2. 本來傳回的 List(Of 改為【 iQueryable(Of 】
  3. 回傳的部分,加上【 .AsQueryable 】

是的,就是這樣小小的三步驟,就可以把他變成 OData 。(灑花)

<Queryable>
Public Function GetValues() As IQueryable(Of ProductInfo)
    Dim ProdObj As New ProductDAO
    Dim oProds As List(Of ProductInfo) = ProdObj.GetAllProducts()
    Return oProds.AsQueryable
End Function

 

OData 指令

在開始試用之前,先來整理一下幾個常用的OData指令,以及指令的語法

 

Uri規則:

首先必須在本來的Uri之後加上個【?】符號

接著,在每個指令之前,都必須加上一個【$】符號。(這個會不知不覺忘記,所以如果出現的結果不是預期的,可以先檢查是否漏了這個)

指令與指令連接,這部分與之前的QueryString的變數連接一樣,要用【&】符號

 

指令:

指令 說明 範例
top 結果挑出最前面的幾筆 ?$top=3
skip 略過幾筆。可用於分頁顯示 ?$skip=10
orderby 排序 ?$orderby=SupplierID,ProductID
filter 篩選  
  gt : > , 大於 $filter=ProductID gt 10
  lt :  < , 小於 $filter=ProductID lt 10
  ge : >=, 大於等於 $filter=ProductID ge 10
  le : <=, 小於等於 $filter=ProductID le 10
  eq : =, 等於 $filter=ProductID eq 10
  ne : <>, 不等於 $filter=ProductID ne 10

其他的指令,請參考以下官方的說明

http://msdn.microsoft.com/en-us/library/windowsazure/gg312156.aspx

 

試用

接著懷著興奮的心情,就來體驗看看是否能夠直接在Uri設定指令,不改程式的狀況就可以有一些篩選、TOP、排序、略過的功能

首先是直接執行,OK沒問題

001

 

TOP n :選出最先的10筆
發現排序上就不是以ProductID來排

002

 

Top 10 加上依照ProductID來排序

Uri : http://localhost:2639/api/Products?$top=10&$orderby=ProductID

003

 

以ProductID排序 + 略過25筆 + 顯示5筆。

使用情境:以ProductID排序,分頁,一頁顯示5筆,在第6頁

004

 

以UnitPrice倒序排序,選出前5筆

使用情境:顯示最貴的5筆資料

005

 

進一步看OData

從以上的測試,可以在不修改程式的前提下,透過Uri的指令,就可以對查詢的資料進行排序、篩選、top、略過等等的功能,可以在最小的開發成本,獲得相當大的能力。真是不錯。

而小喵的測試,是使用自訂的類別,用的是List<T>的方式,轉成IQueryable<T>,其實資料還是將全部撈取回來後,放在記憶體中,然後再針對記憶體中的資料進行相關的篩選或排序。小喵從黑大的文章裡面得知,其實如果搭配的是Entity Framework或者LINQ to SQL這類的ORM物件的話,以篩選來說,會真正的產生WHERE的SQL語法去對資料庫篩選,這樣記憶體的使用將會更省。相關的文章大家可以參考以下黑大這篇精彩的文章:

關於IQueryable<T>特性的小實驗(黑暗執行緒)

 

總結

OData 的初體驗到此進入尾聲。從這次的初體驗,小喵個人覺得 OData 的確是一個簡單易學、容易開發使用的一個機制,對於WebAPI的應用上,提供了更大的彈性,可以讓我們專注於產出相關的資料,至於資料如何應用,就交由使用端透過 Uri 來決定。

在小喵去 TWMVC 上課的過程中,KKBruce大大還展現了,可以直些將OData的網址,提供給Excel,然後就可以將資料直接在Excel中使用。

很不錯的一套機制,小喵的初體驗在此紀錄一下,也提供大家參考。

 


以下是簽名:


Microsoft MVP
Visual Studio and Development Technologies
(2005~2019/6) 
topcat
Blog:http://www.dotblogs.com.tw/topcat