Condividi tramite


Uso di $select, $expand e $value in API Web ASP.NET 2 OData

di Mike Wasson

Panoramica e esempi di codice per le opzioni di $expand, $select e $value nell'API Web OData 2 per ASP.NET 4.x. Queste opzioni consentono a un client di controllare la rappresentazione restituita dal server.

  • $expand causa l'inserimento inline delle entità correlate nella risposta.
  • $select seleziona un subset di proprietà da includere nella risposta.
  • $value ottiene il valore non elaborato di una proprietà.

Schema di esempio

Per questo articolo si userà un servizio OData che definisce tre entità: Product, Supplier e Category. Ogni prodotto ha una categoria e un fornitore.

Diagramma che mostra uno schema di esempio per il servizio dati O, definendo prodotti, fornitori e categorie come entità.

Di seguito sono riportate le classi C# che definiscono i modelli di 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; }
}

Si noti che la Product classe definisce le proprietà di spostamento per e SupplierCategory. La Category classe definisce una proprietà di navigazione per i prodotti in ogni categoria.

Per creare un endpoint OData per questo schema, usare l'Visual Studio 2013 scaffolding, come descritto in Creazione di un endpoint OData in API Web ASP.NET. Aggiungere controller separati per Product, Category e Supplier.

Abilitazione di $expand e $select

In Visual Studio 2013 l'scaffolding dell'API Web OData crea un controller che supporta automaticamente $expand e $select. Per informazioni di riferimento, ecco i requisiti per supportare $expand e $select in un controller.

Per le raccolte, il metodo del Get controller deve restituire un oggetto IQueryable.

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

Per le singole entità, restituire un oggetto SingleResult<T, dove T> è un oggetto IQueryable contenente zero o una entità.

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

Inoltre, decorare i Get metodi con l'attributo [Queryable] , come illustrato nei frammenti di codice precedenti. In alternativa, chiamare EnableQuerySupport nell'oggetto HttpConfiguration all'avvio. Per altre informazioni, vedere Abilitazione delle opzioni di query OData.

Uso di $expand

Quando si esegue una query su un'entità OData o una raccolta, la risposta predefinita non include entità correlate. Ad esempio, ecco la risposta predefinita per il set di entità Categorie:

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

Come si può notare, la risposta non include prodotti, anche se l'entità Category ha un collegamento di spostamento Prodotti. Tuttavia, il client può usare $expand per ottenere l'elenco di prodotti per ogni categoria. L'opzione $expand passa nella stringa di query della richiesta:

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

Ora il server includerà i prodotti per ogni categoria, inline con le categorie. Ecco il payload della risposta:

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

Si noti che ogni voce della matrice "value" contiene un elenco Prodotti.

L'opzione $expand accetta un elenco delimitato da virgole delle proprietà di spostamento da espandere. La richiesta seguente espande sia la categoria che il fornitore per un prodotto.

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

Ecco il corpo della risposta:

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

È possibile espandere più di un livello di proprietà di spostamento. L'esempio seguente include tutti i prodotti per una categoria e anche il fornitore per ogni prodotto.

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

Ecco il corpo della risposta:

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

Per impostazione predefinita, l'API Web limita la profondità massima di espansione a 2. Ciò impedisce al client di inviare richieste complesse come $expand=Orders/OrderDetails/Product/Supplier/Region, che potrebbero essere inefficienti per eseguire query e creare risposte di grandi dimensioni. Per eseguire l'override dell'impostazione predefinita, impostare la proprietà MaxExpansionDepth sull'attributo [Queryable].

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

Per altre informazioni sull'opzione $expand, vedere Espandere l'opzione Di query di sistema ($expand) nella documentazione ufficiale di OData.

Uso di $select

L'opzione $select specifica un subset di proprietà da includere nel corpo della risposta. Ad esempio, per ottenere solo il nome e il prezzo di ogni prodotto, usare la query seguente:

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

Ecco il corpo della risposta:

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

È possibile combinare $select e $expand nella stessa query. Assicurarsi di includere la proprietà espansa nell'opzione $select. Ad esempio, la richiesta seguente ottiene il nome e il fornitore del prodotto.

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

Ecco il corpo della risposta:

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

È anche possibile selezionare le proprietà all'interno di una proprietà espansa. La richiesta seguente espande Prodotti e seleziona il nome della categoria e il nome del prodotto.

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

Ecco il corpo della risposta:

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

Per altre informazioni sull'opzione $select, vedere Selezionare l'opzione Query di sistema ($select) nella documentazione ufficiale di OData.

Recupero di singole proprietà di un'entità ($value)

Esistono due modi per un client OData per ottenere una singola proprietà da un'entità. Il client può ottenere il valore in formato OData o ottenere il valore non elaborato della proprietà.

La richiesta seguente ottiene una proprietà in formato OData.

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

Ecco una risposta di esempio in formato 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"
}

Per ottenere il valore non elaborato della proprietà, aggiungere $value all'URI:

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

Ecco la risposta. Si noti che il tipo di contenuto è "text/plain", non JSON.

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

Hat

Per supportare queste query nel controller OData, aggiungere un metodo denominato GetProperty, dove Property è il nome della proprietà. Ad esempio, il metodo per ottenere la proprietà Name sarà denominato GetName. Il metodo deve restituire il valore di tale proprietà:

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