在 ASP.NET Web API 2 OData 中使用 $select、$expand 和 $value
作者:Mike Wasson
適用于 ASP.NET 4.x 之 OData Web API 2 中$expand、$select和$value選項的概觀和程式碼範例。 這些選項可讓用戶端控制從伺服器傳回的標記法。
- $expand會導致 回應中包含相關的實體。
- $select 會選取要包含在回應中的屬性子集。
- $value 取得屬性的原始值。
範例架構
在本文中,我將使用定義三個實體的 OData 服務:產品、供應商和類別。 每個產品都有一個類別和一個供應商。
以下是定義實體模型的 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
會定義 和 Category
的 Supplier
導覽屬性。 類別 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 實體或集合時,預設回應不包含相關的實體。 例如,以下是 Categories 實體集的預設回應:
{
"odata.metadata":"http://localhost/odata/$metadata#Categories",
"value":[
{"ID":1,"Name":"Apparel"},
{"ID":2,"Name":"Toys"}
]
}
如您所見,即使 Category 實體有 Products 導覽連結,回應也不會包含任何產品。 不過,用戶端可以使用$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」 陣列中的每個專案都包含 Products 清單。
$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);
}
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應