Podpora vztahů mezi entitami v OData v3 pomocí webového rozhraní API 2

Mike Wasson

Stáhnout dokončený projekt

Většina datových sad definuje vztahy mezi entitami: Zákazníci mají objednávky; knihy mají autory; výrobky mají dodavatele. Pomocí OData můžou klienti procházet vztahy entit. Vzhledem k produktu můžete najít dodavatele. Můžete také vytvořit nebo odebrat relace. Můžete například nastavit dodavatele produktu.

V tomto kurzu se dozvíte, jak tyto operace podporovat ve webovém rozhraní API ASP.NET. Kurz vychází z kurzu Vytvoření koncového bodu OData v3 pomocí webového rozhraní API 2.

Verze softwaru použité v tomto kurzu

  • Webové rozhraní API 2
  • OData verze 3
  • Entity Framework 6

Přidání entity dodavatele

Nejprve musíme do datového kanálu OData přidat nový typ entity. Přidáme Supplier třídu.

using System.ComponentModel.DataAnnotations;

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

Tato třída používá řetězec pro klíč entity. V praxi to může být méně běžné než použití celočíselného klíče. Stojí ale za to vidět, jak OData kromě celých čísel zpracovává i jiné typy klíčů.

Dále vytvoříme relaci přidáním Supplier vlastnosti do Product třídy:

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

Do třídy přidejte novou DbSetProductServiceContext, aby Entity Framework zahrnovala Supplier tabulku do databáze.

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

V souboru WebApiConfig.cs přidejte do modelu EDM entitu Dodavatelé:

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

Pokud chce klient získat dodavatele produktu, odešle požadavek GET:

GET /Products(1)/Supplier

Tady "Dodavatel" je navigační vlastnost pro Product typ. V tomto případě Supplier odkazuje na jednu položku, ale navigační vlastnost může také vrátit kolekci (relace 1:N nebo M:N).

Pro podporu tohoto požadavku přidejte do třídy následující metodu ProductsController :

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

Parametr key je klíč produktu. Metoda vrátí související entitu – v tomto případě Supplier instanci. Název metody a název parametru jsou důležité. Obecně platí, že pokud má vlastnost navigace název "X", musíte přidat metodu s názvem "GetX". Metoda musí přijmout parametr s názvem "key", který odpovídá datovému typu nadřazeného klíče.

Do klíčového parametru je také důležité zahrnout atribut [FromOdataUri]. Tento atribut říká webovému rozhraní API, aby při analýze klíče z identifikátoru URI požadavku používalo pravidla syntaxe OData.

OData podporuje vytváření nebo odebírání relací mezi dvěma entitami. V terminologii OData je relace "odkaz". Každý odkaz má identifikátor URI s entitou formuláře, $links nebo entitou. Například odkaz mezi produktem a dodavatelem vypadá takto:

/Products(1)/$links/Supplier

Pokud chcete vytvořit nový odkaz, klient odešle požadavek POST na identifikátor URI odkazu. Tělo požadavku je identifikátor URI cílové entity. Předpokládejme například, že existuje dodavatel s klíčem CTSO. Pokud chcete vytvořit odkaz z "Product(1)" na "Supplier('CTSO')", odešle klient požadavek podobný následujícímu:

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

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

Pokud chcete odstranit odkaz, klient odešle požadavek DELETE na identifikátor URI odkazu.

Vytváření odkazů

Pokud chcete klientovi umožnit vytváření odkazů na dodavatele produktů, přidejte do ProductsController třídy následující kód:

[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();
    }
}

Tato metoda přijímá tři parametry:

  • key: Klíč nadřazené entity (produkt)
  • navigationProperty: Název vlastnosti navigace. V tomto příkladu je jedinou platnou navigační vlastností "Dodavatel".
  • link: Identifikátor URI OData související entity. Tato hodnota je převzata z textu požadavku. Například identifikátor URI odkazu může být "http://localhost/odata/Suppliers('CTSO'), což znamená dodavatel s ID = CTSO.

Metoda pomocí odkazu vyhledá dodavatele. Pokud je nalezen odpovídající dodavatel, metoda nastaví Product.Supplier vlastnost a uloží výsledek do databáze.

Nejobtížnější částí je parsování identifikátoru URI odkazu. V podstatě musíte simulovat výsledek odeslání požadavku GET na tento identifikátor URI. Následující pomocná metoda ukazuje, jak to udělat. Metoda vyvolá proces směrování webového rozhraní API a získá zpět instanci ODataPath , která představuje analyzovanou cestu OData. U identifikátoru URI odkazu by jedním ze segmentů měl být klíč entity. (Pokud ne, klient odeslal chybný identifikátor URI.)

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

Odstraňují se odkazy

Pokud chcete odstranit odkaz, přidejte do ProductsController třídy následující kód:

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();

    }
}

V tomto příkladu je navigační vlastností jedna Supplier entita. Pokud je vlastnost navigace kolekce, musí identifikátor URI pro odstranění odkazu obsahovat klíč pro související entitu. Příklad:

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

Tato žádost odebere objednávku 1 ze zákazníka 1. V tomto případě bude mít metoda DeleteLink následující podpis:

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

Parametr relatedKey poskytuje klíč pro související entitu. Takže v metodě vyhledejte DeleteLink primární entitu podle parametru klíče , vyhledejte související entitu podle parametru relatedKey a pak odeberte přidružení. V závislosti na datovém modelu může být potřeba implementovat obě verze nástroje DeleteLink. Webové rozhraní API bude volat správnou verzi na základě identifikátoru URI požadavku.