使用 $select,$expand、 和 ASP.NET Web API 2 OData 中的 $valueUsing $select, $expand, and $value in ASP.NET Web API 2 OData

通过Mike Wassonby Mike Wasson

为 $ 的概述和代码示例展开,$select,和 $value 选项 OData Web API 2 中的 ASP.NET 4.x。Overview and code samples for the $expand, $select, and $value options in OData Web API 2 for ASP.NET 4.x. 这些选项允许客户端来控制该通道从服务器返回的表示形式。These options allow a client to control the representation that it gets back from the server.

  • $expand导致相关的实体是以内联形式包含在响应中的。$expand causes related entities to be included inline in the response.
  • $select选择要在响应中包含的属性的子集。$select selects a subset of properties to include in the response.
  • $value获取属性的原始值。$value gets the raw value of a property.

示例架构Example Schema

对于本文中,我将使用 OData 服务,用于定义三个实体:产品、 供应商和类别。For this article, I'll use an OData service that defines three entities: Product, Supplier, and Category. 每个产品都有一个类别和一个供应商。Each product has one category and one supplier.

下面是定义实体模型的 C# 类:Here are the C# classes that define the entity models:

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类定义的导航属性SupplierCategoryNotice that the Product class defines navigation properties for the Supplier and Category. Category类定义中每个类别的产品的导航属性。The Category class defines a navigation property for the products in each category.

若要创建此架构的 OData 终结点,请使用 Visual Studio 2013 的基架,如中所述在 ASP.NET Web API 中创建 OData 终结点To create an OData endpoint for this schema, use the Visual Studio 2013 scaffolding, as described in Creating an OData Endpoint in ASP.NET Web API. 有关产品、 类别和供应商添加单独的控制器。Add separate controllers for Product, Category, and Supplier.

启用 $展开和 $selectEnabling $expand and $select

在 Visual Studio 2013 中,Web API OData 基架创建一个控制器,来自动支持 $expand 和 $select。In Visual Studio 2013, the Web API OData scaffolding creates a controller that automatically supports $expand and $select. 有关参考,下面是支持 $ 要求展开和 $select 控制器中的。For reference, here are the requirements to support $expand and $select in a controller.

对于集合,该控制器Get方法必须返回IQueryableFor collections, the controller's Get method must return an IQueryable.

[Queryable]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

对于单个实体,返回可取值为 SingleResult<T>,其中 T 为IQueryable ,其中包含零个或一个实体。For single entities, return a SingleResult<T>, where T is an IQueryable that contains zero or one entities.

[Queryable]
public SingleResult<Category> GetCategory([FromODataUri] int key)
{
    return SingleResult.Create(db.Categories.Where(c => c.ID == key));
}

此外,修饰您Get方法与 [Queryable] 属性,如前面的代码片段中所示。Also, decorate your Get methods with the [Queryable] attribute, as shown in the previous code snippets. 或者,调用EnableQuerySupportHttpConfiguration在启动时的对象。Alternatively, call EnableQuerySupport on the HttpConfiguration object at startup. (有关详细信息,请参阅启用 OData 查询选项。)(For more information, see Enabling OData Query Options.)

使用 $expandUsing $expand

在查询的 OData 实体或集合时,默认响应不包括相关的实体。When you query an OData entity or collection, the default response does not include related entities. 例如,下面是类别实体集的默认响应:For example, here is the default response for the Categories entity set:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {"ID":1,"Name":"Apparel"},
    {"ID":2,"Name":"Toys"}
  ]
}

正如您所看到的即使类别实体具有一个产品的导航链接响应不包括任何产品。As you can see, the response does not include any products, even though the Category entity has a Products navigation link. 但是,客户端可以使用 $展开此项可获取每个类别的产品列表。However, the client can use $expand to get the list of products for each category. $Expand 选项中请求的查询字符串:The $expand option goes in the query string of the request:

GET http://localhost/odata/Categories?$expand=Products

现在服务器将包括每个类别,各类别的内联的产品。Now the server will include the products for each category, inline with the categories. 下面是响应负载:Here is the response payload:

{
  "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"数组中的每个项包含的产品列表。Notice that each entry in the "value" array contains a Products list.

$ Expand 选项采用以逗号分隔列表与要扩展的导航属性。The $expand option takes a comma-separated list of navigation properties to expand. 以下请求展开该类别和产品的供应商。The following request expands both the category and the supplier for a product.

GET http://localhost/odata/Products(1)?$expand=Category,Supplier

下面是响应正文:Here is the response body:

{
  "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"
}

您可以展开多个导航属性的级别。You can expand more than one level of navigation property. 下面的示例包括一个类别的所有产品以及每个产品的供应商。The following example includes all the products for a category and also the supplier for each product.

GET http://localhost/odata/Categories(1)?$expand=Products/Supplier

下面是响应正文:Here is the response body:

{
  "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 的最大展开深度。By default, Web API limits the maximum expansion depth to 2. 防止发送等复杂请求客户端$expand=Orders/OrderDetails/Product/Supplier/Region,这可能很低效查询并创建大型的响应。That prevents the client from sending complex requests like $expand=Orders/OrderDetails/Product/Supplier/Region, which might be inefficient to query and create large responses. 若要重写默认值,设置MaxExpansionDepth上的属性 [Queryable] 属性。To override the default, set the MaxExpansionDepth property on the [Queryable] attribute.

[Queryable(MaxExpansionDepth=4)]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

详细了解 $ expand 选项,请参阅展开 ($expand) 系统查询选项官方 OData 文档中。For more information about the $expand option, see Expand System Query Option ($expand) in the official OData documentation.

使用 $selectUsing $select

$Select 选项指定要在响应正文中包含的属性的子集。The $select option specifies a subset of properties to include in the response body. 例如,若要获取的名称和每个产品的价格,请使用以下查询:For example, to get only the name and price of each product, use the following query:

GET http://localhost/odata/Products?$select=Price,Name

下面是响应正文:Here is the response body:

{
  "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 中相同的查询。You can combine $select and $expand in the same query. 请确保在 $select 选项中包含扩展的属性。Make sure to include the expanded property in the $select option. 例如,以下请求获取的产品名称和供应商。For example, the following request gets the product name and supplier.

GET http://localhost/odata/Products?$select=Name,Supplier&$expand=Supplier

下面是响应正文:Here is the response body:

{
  "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"
   }
  ]
}

此外可以选择一个扩展属性中的属性。You can also select the properties within an expanded property. 以下请求扩展产品,并选择类别名称和产品名称。The following request expands Products and selects category name plus product name.

GET http://localhost/odata/Categories?$expand=Products&$select=Name,Products/Name

下面是响应正文:Here is the response body:

{
  "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 选项的详细信息,请参阅Select 系统查询选项 ($select)官方 OData 文档中。For more information about the $select option, see Select System Query Option ($select) in the official OData documentation.

获取单个实体 ($value) 的属性Getting Individual Properties of an Entity ($value)

有两种方法可从实体获取的个别属性的 OData 客户端。There are two ways for an OData client to get an individual property from an entity. 客户端可以获取 OData 格式的值或获取属性的原始值。The client can either get the value in OData format, or get the raw value of the property.

以下请求获取 OData 格式的属性。The following request gets a property in OData format.

GET http://localhost/odata/Products(1)/Name

此处是 JSON 格式的示例响应:Here is an example response in JSON format:

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:To get the raw value of the property, append $value to the URI:

GET http://localhost/odata/Products(1)/Name/$value

下面是响应。Here is the response. 请注意,内容类型"text/plain"不是 JSON。Notice that the content type is "text/plain", not JSON.

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Hat

若要在 OData 控制器中支持这些查询,将添加一个名为方法GetProperty,其中Property是属性的名称。To support these queries in your OData controller, add a method named GetProperty, where Property is the name of the property. 例如,若要获取的 Name 属性的方法将被命名为GetNameFor example, the method to get the Name property would be named GetName. 该方法应返回该属性的值:The method should return the value of that property:

public async Task<IHttpActionResult> GetName(int key)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product.Name);
}