Použití $select, $expand a $value v ASP.NET Web API 2 OData

Mike Wasson

Přehled a ukázky kódu pro možnosti $expand, $select a $value ve webovém rozhraní API OData 2 pro ASP.NET 4.x Tyto možnosti umožňují klientovi řídit reprezentaci, kterou získá zpět ze serveru.

  • $expand způsobí, že se do odpovědi zahrnou související entity.
  • $select vybere podmnožinu vlastností, které se mají zahrnout do odpovědi.
  • $value získá nezpracovanou hodnotu vlastnosti.

Příklad schématu

Pro účely tohoto článku použiju službu OData, která definuje tři entity: Produkt, Dodavatel a Kategorie. Každý produkt má jednu kategorii a jednoho dodavatele.

Diagram znázorňující ukázkové schéma pro službu O Data, které definuje produkty, dodavatele a kategorie jako své entity

Tady jsou třídy jazyka C#, které definují modely entit:

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; }
}

Všimněte si, že Product třída definuje vlastnosti navigace pro Supplier a Category. Třída Category definuje vlastnost navigace pro produkty v každé kategorii.

Pokud chcete vytvořit koncový bod OData pro toto schéma, použijte Visual Studio 2013 generování uživatelského rozhraní, jak je popsáno v tématu Vytvoření koncového bodu OData ve webovém rozhraní API ASP.NET. Přidejte samostatné kontrolery pro produkt, kategorii a dodavatele.

Povolení $expand a $select

V Visual Studio 2013 vytvoří generování uživatelského rozhraní OData webového rozhraní API kontroler, který automaticky podporuje $expand a $select. Tady jsou požadavky na podporu $expand a $select v kontroleru.

U kolekcí musí metoda kontroleru Get vrátit IQueryable.

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

U jednotlivých entit vrátí hodnotu SingleResult<T>, kde T je možnost IQueryable obsahující nulu nebo jednu entitu.

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

Metody také ozdobte Get atributem [Queryable], jak je znázorněno v předchozích fragmentech kódu. Případně můžete při spuštění zavolat EnableQuerySupport u objektu HttpConfiguration . (Další informace najdete v tématu Povolení možností dotazů OData.)

Použití $expand

Když se dotazujete na entitu nebo kolekci OData, výchozí odpověď neobsahuje související entity. Tady je například výchozí odpověď pro sadu entit Kategorie:

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

Jak vidíte, odpověď neobsahuje žádné produkty, i když entita Kategorie obsahuje navigační odkaz Produkty. Klient však může použít $expand k získání seznamu produktů pro každou kategorii. Možnost $expand se zobrazí v řetězci dotazu požadavku:

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

Teď bude server obsahovat produkty pro každou kategorii, které jsou v souladu s kategoriemi. Tady je datová část odpovědi:

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

Všimněte si, že každá položka v poli "hodnota" obsahuje seznam Produkty.

Možnost $expand umožňuje rozbalit seznam navigačních vlastností oddělených čárkami. Následující žádost rozšiřuje kategorii i dodavatele produktu.

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

Tady je text odpovědi:

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

Můžete rozbalit více než jednu úroveň vlastnosti navigace. Následující příklad obsahuje všechny produkty pro kategorii a také dodavatele pro každý produkt.

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

Tady je text odpovědi:

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

Ve výchozím nastavení webové rozhraní API omezuje maximální hloubku rozšíření na 2. To brání klientovi v odesílání složitých požadavků, jako $expand=Orders/OrderDetails/Product/Supplier/Regionje , což může být neefektivní při dotazování a vytváření velkých odpovědí. Chcete-li přepsat výchozí, nastavte vlastnost MaxExpansionDepth pro atribut [Queryable].

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

Další informace o možnosti $expand najdete v části Rozbalení možnosti systémového dotazu ($expand) v oficiální dokumentaci OData.

Použití $select

Možnost $select určuje podmnožinu vlastností, které se mají zahrnout do textu odpovědi. Pokud například chcete získat jenom název a cenu jednotlivých produktů, použijte následující dotaz:

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

Tady je text odpovědi:

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

Ve stejném dotazu můžete kombinovat $select a $expand. Nezapomeňte do možnosti $select zahrnout rozbalenou vlastnost. Například následující požadavek získá název produktu a dodavatele.

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

Tady je text odpovědi:

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

Můžete také vybrat vlastnosti v rámci rozbalené vlastnosti. Následující požadavek rozbalí položku Produkty a vybere název kategorie a název produktu.

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

Tady je text odpovědi:

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

Další informace o možnosti $select najdete v části Výběr možnosti systémového dotazu ($select) v oficiální dokumentaci OData.

Získání jednotlivých vlastností entity ($value)

Existují dva způsoby, jak klient OData získat individuální vlastnost z entity. Klient může buď získat hodnotu ve formátu OData, nebo získat nezpracovanou hodnotu vlastnosti.

Následující požadavek získá vlastnost ve formátu OData.

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

Tady je příklad odpovědi ve formátu 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"
}

Pokud chcete získat nezpracovanou hodnotu vlastnosti, připojte k identifikátoru URI $value:

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

Tady je odpověď. Všimněte si, že typ obsahu je "text/prostý", nikoli JSON.

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

Hat

Pokud chcete tyto dotazy podporovat v kontroleru OData, přidejte metodu s názvem GetProperty, kde Property je název vlastnosti. Například metoda pro získání vlastnosti Name by se jmenovala GetName. Metoda by měla vrátit hodnotu této vlastnosti:

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