共用方式為


在 ASP.NET Web API 2 OData 中使用 $select、$expand 和 $value

演講者:Mike Wasson

OData Web API 2 for ASP.NET 4.x 中 $expand、$select 和 $value 選項的概述和程式碼範例。 這些選項允許用戶端控制從伺服器傳回的表示形式。

  • $expand 會導致相關實體內嵌包含在回應中。
  • $select 選擇要包含在回應中的屬性子集。
  • $value 取得屬性的原始值。

範例結構描述

在本文中,我將使用定義三個實體的 OData 服務:產品、供應商和類別。 每種產品都有一個類別和一個供應商。

此圖顯示了 O 資料服務的範例結構描述,將產品、供應商和類別定義為其實體。

以下是定義實體模型的 C# 類別:

public class Supplier
{
    [Key]
    public string Key {get; set; }
    public string Name { get; set; }
}
public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [ForeignKey("Category")]
    public int CategoryId { get; set; }
    public Category Category { get; set; }

    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

請注意,Product 類別定義了 SupplierCategory 的導覽屬性。 Category 類別為每個類別中的產品定義導覽屬性。

若要為此結構描述建立 OData 終端點,請使用 Visual Studio 2013 Scaffolding,如在「ASP.NET Web API 中建立 OData 終端點」中所述。 為「產品」、「類別」和「供應商」新增單獨的控制器。

啟用 $expand 和 $select

在 Visual Studio 2013 中,Web API OData Scaffolding 會建立一個自動支援 $expand 和 $select 的控制器。 作為參考,以下是在控制器中支援 $expand 和 $select 的要求。

對於集合,控制器的 Get 方法必須傳回 IQueryable

[Queryable]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

對於單一實體,傳回 SingleResult<T>,其中 T 是包含零個或一個實體的 IQueryable

[Queryable]
public SingleResult<Category> GetCategory([FromODataUri] int key)
{
    return SingleResult.Create(db.Categories.Where(c => c.ID == key));
}

另外,使用 [Queryable] 屬性裝飾 Get 方法,如前面的程式碼片段所示。 或者,在啟動時對 HttpConfiguration 物件呼叫 EnableQuerySupport。 (有關詳細資訊,請參閱「啟用 OData 查詢選項」。)

使用 $expand

當您查詢 OData 實體或集合時,預設回應不包含相關實體。 例如,以下是類別實體集的預設回應:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {"ID":1,"Name":"Apparel"},
    {"ID":2,"Name":"Toys"}
  ]
}

正如您所看到的,即使類別實體具有產品導覽連結,回應也不包含任何產品。 但是,用戶端可以使用 $expand 來取得每個類別的產品清單。 $expand 選項出現在請求的查詢字串中:

GET http://localhost/odata/Categories?$expand=Products

現在,伺服器將包含每個類別的產品,與類別一致。 這是回應酬載:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {
      "Products":[
        {"ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"}
      ],
      "ID":1,
      "Name":"Apparel"
    },
    {
      "Products":[
        {"ID":4,"Name":"Yo-yo","Price":"4.95","CategoryId":2,"SupplierId":"WING"},
        {"ID":5,"Name":"Puzzle","Price":"8.00","CategoryId":2,"SupplierId":"WING"}
      ],
      "ID":2,
      "Name":"Toys"
    }
  ]
}

請注意,「value」陣列中的每個項目都包含一個產品清單。

$expand 選項會採用逗號分隔的導覽屬性清單來展開。 以下請求擴展了產品的類別和供應商。

GET http://localhost/odata/Products(1)?$expand=Category,Supplier

這是回應本文:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products/@Element",
  "Category": {"ID":1,"Name":"Apparel"},
  "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "CategoryId":1,
  "SupplierId":"CTSO"
}

您可以展開多個層級的導覽屬性。 以下範例包括某個類別的所有產品以及每個產品的供應商。

GET http://localhost/odata/Categories(1)?$expand=Products/Supplier

這是回應本文:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories/@Element",
  "Products":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"
    },{
      "Supplier":{
        "Key":"FBRK","Name":"Fabrikam, Inc."
      },"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"
    }
  ],"ID":1,"Name":"Apparel"
}

預設情況下,Web API 將最大擴充深度限制為 2。 這可以防止用戶端傳送複雜的請求,例如 $expand=Orders/OrderDetails/Product/Supplier/Region,這可能會降低查詢和建立大型回應的效率。 若要覆寫預設值,請在 [Queryable] 屬性上設定 MaxExpansionDepth 屬性。

[Queryable(MaxExpansionDepth=4)]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

有關 $expand 選項的更多資訊,請參閱官方 OData 文件中的擴展系統查詢選項 ($expand)

使用 $select

$select 選項指定要包含在回應本文中的屬性子集。 例如,若要僅取得每個產品的名稱和價格,請使用下列查詢:

GET http://localhost/odata/Products?$select=Price,Name

這是回應本文:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Price,Name",
  "value":[
    {"Price":"15.00","Name":"Hat"},
    {"Price":"12.00","Name":"Scarf"},
    {"Price":"5.00","Name":"Socks"},
    {"Price":"4.95","Name":"Yo-yo"},
    {"Price":"8.00","Name":"Puzzle"}
  ]
}

您可以在同一查詢中組合 $select 和 $expand。 確保在 $select 選項中包含擴充屬性。 例如,以下請求取得產品名稱和供應商。

GET http://localhost/odata/Products?$select=Name,Supplier&$expand=Supplier

這是回應本文:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Name,Supplier",
  "value":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Hat"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Scarf"
    },
    {
      "Supplier":{"Key":"FBRK","Name":"Fabrikam, Inc."},
      "Name":"Socks"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Yo-yo"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Puzzle"
   }
  ]
}

您也可以選擇擴充屬性中的屬性。 以下請求擴展產品並選擇類別名稱和產品名稱。

GET http://localhost/odata/Categories?$expand=Products&$select=Name,Products/Name

這是回應本文:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories&$select=Name,Products/Name",
  "value":[ 
    {
      "Products":[ {"Name":"Hat"},{"Name":"Scarf"},{"Name":"Socks"} ],
      "Name":"Apparel"
    },
    {
      "Products":[ {"Name":"Yo-yo"},{"Name":"Puzzle"} ],
      "Name":"Toys"
    }
  ]
}

有關 $select 選項的更多資訊,請參閱官方 OData 文件中的「選擇系統查詢選項 ($select)」。

取得實體的各個屬性 ($value)

OData 用戶端有兩種方法從實體取得單一屬性。 用戶端可以取得 OData 格式的值,也可以取得屬性的原始值。

以下請求取得 OData 格式的屬性。

GET http://localhost/odata/Products(1)/Name

以下是 JSON 格式的回應範例:

HTTP/1.1 200 OK
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 90

{
  "odata.metadata":"http://localhost:14239/odata/$metadata#Edm.String",
  "value":"Hat"
}

若要取得屬性的原始值,請將 $value 附加到 URI:

GET http://localhost/odata/Products(1)/Name/$value

這是回應。 請注意,內容類型是「text/plain」,而不是 JSON。

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Hat

若要在 OData 控制器中支援這些查詢,請新增一個名為 GetProperty 的方法,其中 Property 是屬性的名稱。 例如,取得 Name 屬性的方法將命名為 GetName。 該方法應傳回該屬性的值:

public async Task<IHttpActionResult> GetName(int key)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product.Name);
}