使用 Web API 2 支援 OData v3 中的實體關聯
作者:Mike Wasson
大部分資料集都會定義實體之間的關聯:客戶有訂單;書籍有作者;產品有供應商。 使用 OData 時,用戶端可以流覽實體關聯。 假設有產品,您可以找到供應商。 您也可以建立或移除關聯性。 例如,您可以設定產品的供應商。
本教學課程說明如何在 ASP.NET Web API 中支援這些作業。 本教學課程是以 使用 Web API 2 建立 OData v3 端點教學課程為基礎。
教學課程中使用的軟體版本
- Web API 2
- OData 第 3 版
- Entity Framework 6
新增供應商實體
首先,我們需要將新的實體類型新增至 OData 摘要。 我們將新增 Supplier
類別。
using System.ComponentModel.DataAnnotations;
namespace ProductService.Models
{
public class Supplier
{
[Key]
public string Key { get; set; }
public string Name { get; set; }
}
}
這個類別使用實體索引鍵的字串。 實際上,這可能比使用整數索引鍵還少。 但值得看看 OData 如何處理整數以外的其他索引鍵類型。
接下來,我們會將 屬性新增 Supplier
至 Product
類別來建立關聯:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
// New code
[ForeignKey("Supplier")]
public string SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
將新的 DbSet 新增至 ProductServiceContext
類別,讓 Entity Framework 將資料表包含在 Supplier
資料庫中。
public class ProductServiceContext : DbContext
{
public ProductServiceContext() : base("name=ProductServiceContext")
{
}
public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
// New code:
public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}
在 WebApiConfig.cs 中,將 「Suppliers」 實體新增至 EDM 模型:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");
導覽屬性
若要取得產品的供應商,用戶端會傳送 GET 要求:
GET /Products(1)/Supplier
這裡的 「供應商」是類型上的 Product
導覽屬性。 在此情況下, Supplier
是指單一專案,但導覽屬性也可以傳回集合 (一對多或多對多關聯性) 。
若要支援此要求,請將下列方法新增至 ProductsController
類別:
// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
Product product = _context.Products.FirstOrDefault(p => p.ID == key);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product.Supplier;
}
key參數是產品的索引鍵。 方法會傳回相關的實體,在此案例中為 Supplier
實例。 方法名稱和參數名稱都很重要。 一般而言,如果導覽屬性名為 「X」,您需要新增名為 「GetX」 的方法。 方法必須採用名為 「key」 的參數,以符合父索引鍵的資料類型。
在key參數中包含[FromOdataUri]屬性也很重要。 當 Web API 從要求 URI 剖析金鑰時,此屬性會告訴 Web API 使用 OData 語法規則。
建立和刪除連結
OData 支援建立或移除兩個實體之間的關聯性。 在 OData 術語中,關聯性是「連結」。每個連結都有一個 URI,其中包含表單 實體/$links/實體。 例如,從產品到供應商的連結看起來像這樣:
/Products(1)/$links/Supplier
若要建立新的連結,用戶端會將 POST 要求傳送至連結 URI。 要求的主體是目標實體的 URI。 例如,假設有一個供應商具有金鑰 「CTSO」。 若要從 「產品 (1) 」建立連結至「供應商 ('CTSO') 」,用戶端會傳送如下的要求:
POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50
{"url":"http://localhost/odata/Suppliers('CTSO')"}
若要刪除連結,用戶端會將 DELETE 要求傳送至連結 URI。
建立連結
若要讓用戶端建立產品供應商連結,請將下列程式碼新增至 ProductsController
類別:
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
string supplierKey = GetKeyFromLinkUri<string>(link);
Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
if (supplier == null)
{
return NotFound();
}
product.Supplier = supplier;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
此方法採用三個參數:
- 索引鍵:產品) (父實體的索引鍵
- navigationProperty:導覽屬性的名稱。 在此範例中,唯一有效的導覽屬性是 「Supplier」。
- link:相關實體的 OData URI。 此值取自要求本文。 例如,連結 URI 可能是 「
http://localhost/odata/Suppliers('CTSO')
,這表示識別碼為 'CTSO' 的供應商。
方法會使用 連結來查閱供應商。 如果找到相符的供應商,此方法會設定 屬性, Product.Supplier
並將結果儲存至資料庫。
最硬的部分是剖析連結 URI。 基本上,您需要模擬將 GET 要求傳送至該 URI 的結果。 下列協助程式方法示範如何執行這項操作。 方法會叫用 Web API 路由程式,並傳回代表已剖析 OData 路徑的 ODataPath 實例。 針對連結 URI,其中一個區段應該是實體索引鍵。 (如果不是,用戶端傳送了不正確的 URI.)
// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
TKey key = default(TKey);
// Get the route that was used for this request.
IHttpRoute route = Request.GetRouteData().Route;
// Create an equivalent self-hosted route.
IHttpRoute newRoute = new HttpRoute(route.RouteTemplate,
new HttpRouteValueDictionary(route.Defaults),
new HttpRouteValueDictionary(route.Constraints),
new HttpRouteValueDictionary(route.DataTokens), route.Handler);
// Create a fake GET request for the link URI.
var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);
// Send this request through the routing process.
var routeData = newRoute.GetRouteData(
Request.GetConfiguration().VirtualPathRoot, tmpRequest);
// If the GET request matches the route, use the path segments to find the key.
if (routeData != null)
{
ODataPath path = tmpRequest.GetODataPath();
var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
if (segment != null)
{
// Convert the segment into the key type.
key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
segment.Value, ODataVersion.V3);
}
}
return key;
}
刪除連結
若要刪除連結,請將下列程式碼新增至 ProductsController
類別:
public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
product.Supplier = null;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
在此範例中,導覽屬性是單 Supplier
一實體。 如果導覽屬性是集合,則刪除連結的 URI 必須包含相關實體的索引鍵。 例如:
DELETE /odata/Customers(1)/$links/Orders(1)
此要求會從客戶 1 移除訂單 1。 在此情況下,DeleteLink 方法會有下列簽章:
void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);
relatedKey參數會提供相關實體的索引鍵。 因此,在您的 DeleteLink
方法中,依 key 參數查閱主要實體、依 relatedKey 參數尋找相關實體,然後移除關聯。 視您的資料模型而定,您可能需要實作這兩個版本的 DeleteLink
。 Web API 會根據要求 URI 呼叫正確的版本。
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應