Freigeben über


Unterstützung von Entitätsbeziehungen in OData v3 mit Web-API 2

von Mike Wasson

Abgeschlossenes Projekt herunterladen

Die meisten Datasets definieren Beziehungen zwischen Entitäten: Kunden haben Aufträge; Bücher haben Autoren; Produkte haben Lieferanten. Mithilfe von OData können Clients über Entitätsbeziehungen navigieren. Bei einem Produkt können Sie den Lieferanten finden. Sie können auch Beziehungen erstellen oder entfernen. Beispielsweise können Sie den Lieferanten für ein Produkt festlegen.

In diesem Tutorial erfahren Sie, wie Sie diese Vorgänge in ASP.NET-Web-API unterstützen. Das Tutorial basiert auf dem Tutorial Erstellen eines OData v3-Endpunkts mit Web-API 2.

Im Tutorial verwendete Softwareversionen

  • Web-API 2
  • OData Version 3
  • Entity Framework 6

Hinzufügen einer Lieferantenentität

Zunächst müssen wir unserem OData-Feed einen neuen Entitätstyp hinzufügen. Wir fügen eine Klasse hinzu Supplier .

using System.ComponentModel.DataAnnotations;

namespace ProductService.Models
{
    public class Supplier
    {
        [Key]
        public string Key { get; set; }
        public string Name { get; set; }
    }
}

Diese Klasse verwendet eine Zeichenfolge für den Entitätsschlüssel. In der Praxis ist dies möglicherweise weniger üblich als die Verwendung eines ganzzahligen Schlüssels. Es lohnt sich jedoch, zu sehen, wie OData andere Schlüsseltypen neben ganzzahlen behandelt.

Als Nächstes erstellen wir eine Beziehung, indem wir der Product -Klasse eine Supplier -Eigenschaft hinzufügen:

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }

    // New code
    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

Fügen Sie der ProductServiceContext -Klasse ein neues DbSet hinzu, damit Entity Framework die Supplier Tabelle in die Datenbank einschließt.

public class ProductServiceContext : DbContext
{
    public ProductServiceContext() : base("name=ProductServiceContext")
    {
    }

    public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
    // New code:
    public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}

Fügen Sie in WebApiConfig.cs dem EDM-Modell eine Entität "Suppliers" hinzu:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");

Um den Lieferanten für ein Produkt zu erhalten, sendet der Client eine GET-Anforderung:

GET /Products(1)/Supplier

Hier ist "Supplier" eine Navigationseigenschaft für den Product Typ. In diesem Fall Supplier bezieht sich auf ein einzelnes Element, aber eine Navigationseigenschaft kann auch eine Auflistung zurückgeben (1:n- oder Viele-zu-n-Beziehung).

Um diese Anforderung zu unterstützen, fügen Sie der -Klasse die ProductsController folgende Methode hinzu:

// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
    Product product = _context.Products.FirstOrDefault(p => p.ID == key);
    if (product == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return product.Supplier;
}

Der Schlüsselparameter ist der Schlüssel des Produkts. Die Methode gibt die zugehörige Entität zurück, in diesem Fall eine Supplier instance. Der Methodenname und der Parametername sind wichtig. Wenn die Navigationseigenschaft "X" heißt, müssen Sie im Allgemeinen eine Methode mit dem Namen "GetX" hinzufügen. Die Methode muss einen Parameter namens "key" verwenden, der dem Datentyp des übergeordneten Schlüssels entspricht.

Es ist auch wichtig, das [FromOdataUri] -Attribut in den Schlüsselparameter aufzunehmen. Dieses Attribut weist die Web-API an, OData-Syntaxregeln zu verwenden, wenn sie den Schlüssel aus dem Anforderungs-URI analysiert.

OData unterstützt das Erstellen oder Entfernen von Beziehungen zwischen zwei Entitäten. In der OData-Terminologie ist die Beziehung ein "Link". Jeder Link verfügt über einen URI mit der Formularentität/$links/Entität. Der Link vom Produkt zum Lieferanten sieht beispielsweise wie folgt aus:

/Products(1)/$links/Supplier

Um einen neuen Link zu erstellen, sendet der Client eine POST-Anforderung an den Link-URI. Der Text der Anforderung ist der URI der Zielentität. Angenommen, es gibt einen Lieferanten mit dem Schlüssel "CTSO". Um einen Link von "Product(1)" zu "Supplier('CTSO')" zu erstellen, sendet der Kunde eine Anforderung wie die folgende:

POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50

{"url":"http://localhost/odata/Suppliers('CTSO')"}

Um einen Link zu löschen, sendet der Client eine DELETE-Anforderung an den Link-URI.

Erstellen von Verknüpfungen

Fügen Sie der Klasse den folgenden Code hinzu, um einem Client das Erstellen von Produktlieferantlinks ProductsController zu ermöglichen:

[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
            
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
            
    switch (navigationProperty)
    {
        case "Supplier":
            string supplierKey = GetKeyFromLinkUri<string>(link);
            Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
            if (supplier == null)
            {
                return NotFound();
            }
            product.Supplier = supplier;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();
    }
}

Diese Methode nimmt drei Parameter an:

  • key: Der Schlüssel für die übergeordnete Entität (das Produkt)
  • navigationProperty: Der Name der Navigationseigenschaft. In diesem Beispiel ist die einzige gültige Navigationseigenschaft "Supplier".
  • Link: Der OData-URI der entsprechenden Entität. Dieser Wert wird dem Anforderungstext entnommen. Der Link-URI kann beispielsweise ""http://localhost/odata/Suppliers('CTSO') sein, d. h. der Lieferant mit der ID = 'CTSO'.

Die -Methode verwendet den Link, um den Lieferanten zu suchen. Wenn der übereinstimmende Lieferant gefunden wird, legt die -Methode die Product.Supplier -Eigenschaft fest und speichert das Ergebnis in der Datenbank.

Der schwierigste Teil ist die Analyse des Link-URI. Grundsätzlich müssen Sie das Ergebnis des Sendens einer GET-Anforderung an diesen URI simulieren. Die folgende Hilfsmethode zeigt, wie dies geschieht. Die -Methode ruft den Web-API-Routingprozess auf und ruft einen ODataPath-instance zurück, der den analysierten OData-Pfad darstellt. Für einen Link-URI sollte eines der Segmente der Entitätsschlüssel sein. (Andernfalls hat der Client einen fehlerhaften URI gesendet.)

// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
    TKey key = default(TKey);

    // Get the route that was used for this request.
    IHttpRoute route = Request.GetRouteData().Route;

    // Create an equivalent self-hosted route. 
    IHttpRoute newRoute = new HttpRoute(route.RouteTemplate, 
        new HttpRouteValueDictionary(route.Defaults), 
        new HttpRouteValueDictionary(route.Constraints),
        new HttpRouteValueDictionary(route.DataTokens), route.Handler);

    // Create a fake GET request for the link URI.
    var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);

    // Send this request through the routing process.
    var routeData = newRoute.GetRouteData(
        Request.GetConfiguration().VirtualPathRoot, tmpRequest);

    // If the GET request matches the route, use the path segments to find the key.
    if (routeData != null)
    {
        ODataPath path = tmpRequest.GetODataPath();
        var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
        if (segment != null)
        {
            // Convert the segment into the key type.
            key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
                segment.Value, ODataVersion.V3);
        }
    }
    return key;
}

Links löschen

Um einen Link zu löschen, fügen Sie der Klasse den ProductsController folgenden Code hinzu:

public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }

    switch (navigationProperty)
    {
        case "Supplier":
            product.Supplier = null;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();

    }
}

In diesem Beispiel ist die Navigationseigenschaft eine einzelne Supplier Entität. Wenn die Navigationseigenschaft eine Auflistung ist, muss der URI zum Löschen eines Links einen Schlüssel für die zugehörige Entität enthalten. Zum Beispiel:

DELETE /odata/Customers(1)/$links/Orders(1)

Durch diese Anforderung wird Auftrag 1 von Kunde 1 entfernt. In diesem Fall weist die DeleteLink-Methode die folgende Signatur auf:

void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);

Der relatedKey-Parameter gibt den Schlüssel für die zugehörige Entität an. Suchen Sie also in Ihrer DeleteLink Methode nach der primären Entität nach dem Schlüsselparameter , suchen Sie die zugehörige Entität nach dem relatedKey-Parameter , und entfernen Sie dann die Zuordnung. Abhängig von Ihrem Datenmodell müssen Sie möglicherweise beide Versionen von DeleteLinkimplementieren. Die Web-API ruft die richtige Version basierend auf dem Anforderungs-URI auf.