Freigeben über


Verwenden von $select, $expand und $value in ASP.NET-Web-API 2 OData

von Mike Wasson

Übersicht und Codebeispiele für die Optionen $expand, $select und $value in OData Web API 2 für ASP.NET 4.x. Diese Optionen ermöglichen es einem Client, die Darstellung zu steuern, die er vom Server zurück erhält.

  • $expand bewirkt, dass verwandte Entitäten inline in die Antwort eingeschlossen werden.
  • $select wählt eine Teilmenge der Eigenschaften aus, die in die Antwort eingeschlossen werden sollen.
  • $value ruft den Rohwert einer Eigenschaft ab.

Beispielschema

Für diesen Artikel verwende ich einen OData-Dienst, der drei Entitäten definiert: Product, Supplier und Category. Jedes Produkt hat eine Kategorie und einen Lieferanten.

Diagramm, das ein Beispielschema für den O Data-Dienst zeigt, in dem Produkte, Lieferanten und Kategorien als Entitäten definiert werden.

Hier sind die C#-Klassen, die die Entitätsmodelle definieren:

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

Beachten Sie, dass die Product -Klasse Navigationseigenschaften für und SupplierCategorydefiniert. Die Category -Klasse definiert eine Navigationseigenschaft für die Produkte in jeder Kategorie.

Verwenden Sie zum Erstellen eines OData-Endpunkts für dieses Schema das Visual Studio 2013 Gerüstbau, wie unter Erstellen eines OData-Endpunkts in ASP.NET-Web-API beschrieben. Fügen Sie separate Controller für Product, Category und Supplier hinzu.

Aktivieren von $expand und $select

In Visual Studio 2013 erstellt das OData-Gerüstbau der Web-API einen Controller, der automatisch $expand und $select unterstützt. Hier finden Sie die Anforderungen zur Unterstützung $expand und $select in einem Controller.

Für Sammlungen muss die -Methode des Get Controllers ein IQueryable zurückgeben.

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

Geben Sie für einzelne Entitäten ein SingleResult<T> zurück, wobei T ein IQueryable-Objekt ist, das null oder eine Entität enthält.

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

Dekorieren Sie Ihre Get Methoden außerdem mit dem [Queryable] -Attribut, wie in den vorherigen Codeausschnitten gezeigt. Rufen Sie alternativ enableQuerySupport für das HttpConfiguration-Objekt beim Start auf. (Weitere Informationen finden Sie unter Aktivieren von OData-Abfrageoptionen.)

Verwenden von $expand

Wenn Sie eine OData-Entität oder -Auflistung abfragen, enthält die Standardantwort keine zugehörigen Entitäten. Dies ist beispielsweise die Standardantwort für den Entitätssatz Categories:

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

Wie Sie sehen, enthält die Antwort keine Produkte, auch wenn die Entität Kategorie über einen Navigationslink Für Produkte verfügt. Der Client kann jedoch $expand verwenden, um die Liste der Produkte für jede Kategorie abzurufen. Die Option $expand wird in der Abfragezeichenfolge der Anforderung verwendet:

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

Nun schließt der Server die Produkte für jede Kategorie inline mit den Kategorien ein. Hier sehen Sie die Antwortnutzlast:

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

Beachten Sie, dass jeder Eintrag im Array "Value" eine Products-Liste enthält.

Die Option $expand verwendet eine durch Trennzeichen getrennte Liste von Navigationseigenschaften, um sie zu erweitern. Die folgende Anforderung erweitert sowohl die Kategorie als auch den Lieferanten für ein Produkt.

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

Hier ist der Antworttext:

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

Sie können mehrere Ebenen der Navigationseigenschaft erweitern. Das folgende Beispiel enthält alle Produkte für eine Kategorie und auch den Lieferanten für jedes Produkt.

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

Hier ist der Antworttext:

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

Standardmäßig beschränkt die Web-API die maximale Erweiterungstiefe auf 2. Dadurch wird verhindert, dass der Client komplexe Anforderungen wie $expand=Orders/OrderDetails/Product/Supplier/Regionsendet, die möglicherweise ineffizient sind, um große Antworten abzufragen und zu erstellen. Um den Standardwert zu überschreiben, legen Sie die MaxExpansionDepth-Eigenschaft für das [Queryable]- Attribut fest.

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

Weitere Informationen zur option $expand finden Sie unter Erweitern der Systemabfrageoption ($expand) in der offiziellen OData-Dokumentation.

Verwenden von $select

Die Option $select gibt eine Teilmenge der Eigenschaften an, die in den Antworttext eingeschlossen werden sollen. Verwenden Sie beispielsweise die folgende Abfrage, um nur den Namen und den Preis der einzelnen Produkte abzurufen:

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

Hier ist der Antworttext:

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

Sie können $select und $expand in derselben Abfrage kombinieren. Stellen Sie sicher, dass Sie die erweiterte Eigenschaft in die Option $select einschließen. Die folgende Anforderung ruft beispielsweise den Produktnamen und den Lieferanten ab.

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

Hier ist der Antworttext:

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

Sie können auch die Eigenschaften innerhalb einer erweiterten Eigenschaft auswählen. Die folgende Anforderung erweitert Produkte und wählt Kategoriename plus Produktname aus.

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

Hier ist der Antworttext:

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

Weitere Informationen zur option $select finden Sie unter Auswählen der Systemabfrageoption ($select) in der offiziellen OData-Dokumentation.

Abrufen einzelner Eigenschaften einer Entität ($value)

Es gibt zwei Möglichkeiten für einen OData-Client, eine einzelne Eigenschaft von einer Entität abzurufen. Der Client kann entweder den Wert im OData-Format abrufen oder den Rohwert der Eigenschaft abrufen.

Die folgende Anforderung ruft eine Eigenschaft im OData-Format ab.

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

Hier sehen Sie eine Beispielantwort im 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"
}

Um den Rohwert der Eigenschaft abzurufen, fügen Sie $value an den URI an:

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

Hier ist die Antwort. Beachten Sie, dass der Inhaltstyp "text/plain" und nicht JSON ist.

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

Hat

Um diese Abfragen in Ihrem OData-Controller zu unterstützen, fügen Sie eine Methode mit dem Namen hinzu GetProperty, wobei Property der Name der Eigenschaft ist. Die Methode zum Abrufen der Name-Eigenschaft lautet GetNamebeispielsweise . Die -Methode sollte den Wert dieser Eigenschaft zurückgeben:

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