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 엔드포인트를 만들려면 ASP.NET Web API OData 엔드포인트 만들기에 설명된 대로 Visual Studio 2013 스캐폴딩을 사용합니다. 제품, 범주 및 공급자에 대해 별도의 컨트롤러를 추가합니다.
$expand 및 $select 사용
Visual Studio 2013 Web API OData 스캐폴딩은 $expand 및 $select 자동으로 지원하는 컨트롤러를 만듭니다. 참고로 컨트롤러에서 $expand 및 $select 지원하기 위한 요구 사항은 다음과 같습니다.
컬렉션의 경우 컨트롤러의 Get
메서드는 IQueryable을 반환해야 합니다.
[Queryable]
public IQueryable<Category> GetCategories()
{
return db.Categories;
}
단일 엔터티의 경우 SingleResult<T>를 반환합니다. 여기서 T는 0개 또는 1개의 엔터티를 포함하는 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"}
]
}
보듯이 범주 엔터티에 제품 탐색 링크가 있더라도 응답에는 제품이 포함되지 않습니다. 그러나 클라이언트는 $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"
}
속성의 원시 값을 얻으려면 URI에 $value 추가합니다.
GET http://localhost/odata/Products(1)/Name/$value
응답은 다음과 같습니다. 콘텐츠 형식은 JSON이 아닌 "text/plain"입니다.
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);
}