Web API 2 を使用した OData v3 でのエンティティ関係のサポート
作成者: Mike Wasson
ほとんどのデータ セットはエンティティ間の関係を定義します。顧客には注文があります。書籍には著者がいます。製品にはサプライヤーがいます。 OData を使用すると、クライアントはエンティティ関係をナビゲートできます。 製品を指定すると、サプライヤーを見つけることができます。 リレーションシップを作成または削除することもできます。 たとえば、製品の仕入先を設定できます。
このチュートリアルでは、ASP.NET Web API でこれらの操作をサポートする方法について説明します。 このチュートリアルは、チュートリアル「Web API 2 を使用した OData v3 エンドポイントの作成」に基づいています。
チュートリアルで使用するソフトウェアのバージョン
- Web API 2
- OData Version 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 が整数以外のキー型を処理する方法を確認する価値があります。
次に、 Product
クラスに Supplier
プロパティを追加してリレーションを作成します。
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で、EDM モデルに "Suppliers" エンティティを追加します。
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");
ナビゲーション プロパティ
製品のサプライヤーを取得するために、クライアントは GET 要求を送信します。
GET /Products(1)/Supplier
ここで、"Supplier" は型の Product
ナビゲーション プロパティです。 この場合、Supplier
は 1 つの項目を参照しますが、ナビゲーション プロパティはコレクション (一対多または多対多の関係) を返すこともできます。
この要求をサポートするには、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] 属性を含めることも重要です。 この属性は、要求 URI からキーを解析するときに OData 構文規則を使用するように Web API に指示します。
リンクの作成と削除
OData では、2 つのエンティティ間のリレーションシップの作成または削除がサポートされています。 OData の用語では、リレーションシップは "リンク" です。各リンクには、 フォーム エンティティ/$links/エンティティを含む URI があります。 たとえば、製品からサプライヤーへのリンクは次のようになります。
/Products(1)/$links/Supplier
新しいリンクを作成するために、クライアントはリンク URI に POST 要求を送信します。 要求の本文は、ターゲット エンティティの URI です。 たとえば、キー "CTSO" を持つサプライヤーがあるとします。 "Product(1)" から "Supplier('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();
}
}
このメソッドには、次の 3 つのパラメーターがあります。
- key: 親エンティティ (製品) のキー
- navigationProperty: ナビゲーション プロパティの名前。 この例では、有効なナビゲーション プロパティは "Supplier" のみです。
- link: 関連エンティティの OData URI。 この値は要求本文から取得されます。 たとえば、リンク URI は "
http://localhost/odata/Suppliers('CTSO')
" になる場合があります。これは、ID = 'CTSO' のサプライヤーを意味します。
このメソッドでは、リンクを使用してサプライヤーを検索します。 一致するサプライヤーが見つかった場合、メソッドは Product.Supplier
プロパティを設定し、結果をデータベースに保存します。
最も難しい部分は、リンク URI の解析です。 基本的には、その URI に GET 要求を送信した結果をシミュレートする必要があります。 次のヘルパー メソッドは、これを行う方法を示しています。 メソッドは Web API ルーティング プロセスを呼び出し、解析された OData パスを表す ODataPath インスタンスを取得します。 リンク URI の場合、セグメントの 1 つがエンティティ キーである必要があります。 (そうでない場合、クライアントは無効な 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();
}
}
この例では、ナビゲーション プロパティは 1 つの 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 に基づいて正しいバージョンを呼び出します。