다음을 통해 공유


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 서비스를 사용합니다. 각 제품에는 하나의 범주와 하나의 공급업체가 있습니다.

제품, 공급자 및 범주를 엔터티로 정의하는 O Data 서비스에 대한 샘플 스키마를 보여 주는 다이어그램

엔터티 모델을 정의하는 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; }
}

클래스는 ProductCategory에 대한 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);
}