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; }
}
Entity Framework가 데이터베이스에 ProductServiceContext
테이블을 포함하도록 클래스에 새 DbSet을 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
단일 항목을 참조하지만 탐색 속성은 컬렉션을 반환할 수도 있습니다(일 대 다 또는 다 대 다 관계).
이 요청을 지원하려면 클래스에 다음 메서드를 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;
}
키 매개 변수는 제품의 키입니다. 메서드는 관련 엔터티(이 경우 instance)를 Supplier
반환합니다. 메서드 이름과 매개 변수 이름은 모두 중요합니다. 일반적으로 탐색 속성의 이름이 "X"인 경우 "GetX"라는 메서드를 추가해야 합니다. 메서드는 부모 키의 데이터 형식과 일치하는 "key"라는 매개 변수를 사용해야 합니다.
키 매개 변수에 [FromOdataUri] 특성을 포함하는 것도 중요합니다. 이 특성은 요청 URI에서 키를 구문 분석할 때 OData 구문 규칙을 사용하도록 Web API에 지시합니다.
링크 만들기 및 삭제
OData는 두 엔터티 간의 관계를 만들거나 제거할 수 있습니다. 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')"}
링크를 삭제하기 위해 클라이언트는 링크 URI에 DELETE 요청을 보냅니다.
링크 만들기
클라이언트가 제품 공급자 링크를 만들 수 있도록 하려면 클래스에 다음 코드를 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();
}
}
이 메서드는
- key: 부모 엔터티의 키(제품)
- navigationProperty: 탐색 속성의 이름입니다. 이 예제에서 유효한 탐색 속성은 "Supplier"뿐입니다.
- link: 관련 엔터티의 OData URI입니다. 이 값은 요청 본문에서 가져옵니다. 예를 들어 링크 URI는 "
http://localhost/odata/Suppliers('CTSO')
일 수 있습니다. 즉, ID가 'CTSO'인 공급자를 의미합니다.
메서드는 링크를 사용하여 공급자를 조회합니다. 일치하는 공급자가 발견되면 메서드는 속성을 설정하고 Product.Supplier
결과를 데이터베이스에 저장합니다.
가장 어려운 부분은 링크 URI 구문 분석입니다. 기본적으로 GET 요청을 해당 URI로 보내는 결과를 시뮬레이션해야 합니다. 다음 도우미 메서드는 이 작업을 수행하는 방법을 보여줍니다. 메서드는 Web API 라우팅 프로세스를 호출하고 구문 분석된 OData 경로를 나타내는 ODataPath instance 다시 가져옵니다. 링크 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
키 매개 변수로 기본 엔터티를 조회하고 relatedKey 매개 변수로 관련 엔터티를 찾은 다음 연결을 제거합니다. 데이터 모델에 따라 두 버전의 DeleteLink
를 모두 구현해야 할 수 있습니다. Web API는 요청 URI에 따라 올바른 버전을 호출합니다.