OData v3 'de Web API 2 ile varlık Ilişkilerini desteklemeSupporting Entity Relations in OData v3 with Web API 2

, Mike te sonby Mike Wasson

Tamamlanmış projeyi indirDownload Completed Project

Çoğu veri kümesi varlıklar arasındaki ilişkileri tanımlar: müşterilerin siparişleri vardır; Kitaplar yazar. ürünlerin tedarikçileri vardır.Most data sets define relations between entities: Customers have orders; books have authors; products have suppliers. OData kullanarak, istemciler varlık ilişkilerine gidebilir.Using OData, clients can navigate over entity relations. Bir ürün verildiğinde, tedarikçiyi bulabilirsiniz.Given a product, you can find the supplier. Ayrıca ilişkiler oluşturabilir veya kaldırabilirsiniz.You can also create or remove relationships. Örneğin, tedarikçiyi bir ürün için ayarlayabilirsiniz.For example, you can set the supplier for a product.

Bu öğreticide, ASP.NET Web API 'sinde bu işlemleri nasıl destekleyeceği gösterilmektedir.This tutorial shows how to support these operations in ASP.NET Web API. Öğretici, Web API 2 Ile OData v3 uç noktası oluşturmaöğreticisinde oluşturulur.The tutorial builds on the tutorial Creating an OData v3 Endpoint with Web API 2.

Öğreticide kullanılan yazılım sürümleriSoftware versions used in the tutorial

  • Web API 2Web API 2
  • OData sürüm 3OData Version 3
  • Entity Framework 6Entity Framework 6

Tedarikçi varlığı eklemeAdd a Supplier Entity

İlk olarak OData akışınıza yeni bir varlık türü eklememiz gerekiyor.First we need to add a new entity type to our OData feed. Supplier sınıfı ekleyeceğiz.We'll add a Supplier class.

using System.ComponentModel.DataAnnotations;

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

Bu sınıf, varlık anahtarı için bir dize kullanır.This class uses a string for the entity key. Uygulamada, bir tamsayı anahtar kullanmaktan daha az ortak olabilir.In practice, that might be less common than using an integer key. Ancak, OData 'in diğer anahtar türlerini tamsayıların yanı sıra nasıl işleyeceğini de öğrenecektir.But it's worth seeing how OData handles other key types besides integers.

Sonra, Product sınıfına bir Supplier özelliği ekleyerek bir ilişki oluşturacağız:Next, we'll create a relation by adding a Supplier property to the Product class:

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

ProductServiceContext sınıfına yeni bir Dbset ekleyin, böylece Entity Framework Supplier tablosunu veritabanına dahil eder.Add a new DbSet to the ProductServiceContext class, so that Entity Framework will include the Supplier table in the database.

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

WebApiConfig.cs ' de, EDM modeline bir "Suppliers" varlığı ekleyin:In WebApiConfig.cs, add a "Suppliers" entity to the EDM model:

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

Bir ürünün tedarikçilerini almak için istemci bir GET isteği gönderir:To get the supplier for a product, the client sends a GET request:

GET /Products(1)/Supplier

Burada "Tedarikçi" Product türünde bir gezinti özelliğidir.Here "Supplier" is a navigation property on the Product type. Bu durumda Supplier tek bir öğe anlamına gelir, ancak bir gezinti özelliği de bir koleksiyon (bire çok veya çoktan çoğa ilişki) döndürebilir.In this case, Supplier refers to a single item, but a navigation property can also return a collection (one-to-many or many-to-many relation).

Bu isteği desteklemek için, ProductsController sınıfına aşağıdaki yöntemi ekleyin:To support this request, add the following method to the ProductsController class:

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

Anahtar parametresi ürünün anahtarıdır.The key parameter is the key of the product. Yöntemi bu durumda bir Supplier örneği—olan ilgili varlığı döndürür.The method returns the related entity—in this case, a Supplier instance. Yöntem adı ve parametre adı her ikisi de önemlidir.The method name and parameter name are both important. Genel olarak, gezinti özelliği "X" olarak adlandırılmışsa, "GetX" adlı bir yöntem eklemeniz gerekir.In general, if the navigation property is named "X", you need to add a method named "GetX". Yöntem, üst öğenin anahtarının veri türüyle eşleşen "Key" adlı bir parametre almalıdır.The method must take a parameter named "key" that matches the data type of the parent's key.

Anahtar parametresine [Fromodatauri] özniteliğini eklemek de önemlidir.It is also important to include the [FromOdataUri] attribute in the key parameter. Bu öznitelik, Web API 'sini istek URI 'sinden anahtar ayrıştırdığında OData sözdizimi kurallarını kullanmasını söyler.This attribute tells Web API to use OData syntax rules when it parses the key from the request URI.

OData iki varlık arasında ilişki oluşturmayı veya kaldırmayı destekler.OData supports creating or removing relationships between two entities. OData terminolojisinde ilişki bir "bağlantıdır".In OData terminology, the relationship is a "link." Her bağlantının varlık/$Links/varlıkbiçiminde bir URI 'si vardır.Each link has a URI with the form entity/$links/entity. Örneğin, üründen tedarikçiye bağlantı şu şekilde görünür:For example, the link from product to supplier looks like this:

/Products(1)/$links/Supplier

Yeni bir bağlantı oluşturmak için, istemci bağlantı URI 'sine bir POST isteği gönderir.To create a new link, the client sends a POST request to the link URI. İsteğin gövdesi, hedef varlığın URI 'sidir.The body of the request is the URI of the target entity. Örneğin, "CTSO" anahtarına sahip bir tedarikçi olduğunu varsayalım.For example, suppose there is a supplier with the key "CTSO". "Product (1)" öğesinden "tedarikçisine (' CTSO ')" bir bağlantı oluşturmak için, istemci aşağıdakine benzer bir istek gönderir:To create a link from "Product(1)" to "Supplier('CTSO')", the client sends a request like the following:

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

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

Bir bağlantıyı silmek için, istemci bağlantı URI 'sine bir DELETE isteği gönderir.To delete a link, the client sends a DELETE request to the link URI.

Bağlantı oluşturmaCreating Links

Bir istemcinin ürün-tedarikçi bağlantıları oluşturmasını sağlamak için, ProductsController sınıfına aşağıdaki kodu ekleyin:To enable a client to create product-supplier links, add the following code to the ProductsController class:

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

Bu yöntem üç parametre alır:This method takes three parameters:

  • anahtar: ana varlığa yönelik anahtar (ürün)key: The key to the parent entity (the product)
  • NavigationProperty: Gezinti özelliğinin adı.navigationProperty: The name of the navigation property. Bu örnekte, geçerli olan tek gezinti özelliği "Tedarikçi" dir.In this example, the only valid navigation property is "Supplier".
  • bağlantı: Ilgili varlığın OData URI 'si.link: The OData URI of the related entity. Bu değer, istek gövdesinden alınır.This value is taken from the request body. Örneğin, bağlantı URI 'SI "http://localhost/odata/Suppliers('CTSO'), yani, ID = ' CTSO ' olan tedarikçide olabilir.For example, the link URI might be "http://localhost/odata/Suppliers('CTSO'), meaning the supplier with ID = ‘CTSO'.

Yöntemi, tedarikçiyi aramak için bağlantıyı kullanır.The method uses the link to look up the supplier. Eşleşen Tedarikçi bulunursa, yöntemi Product.Supplier özelliğini ayarlar ve sonucu veritabanına kaydeder.If the matching supplier is found, the method sets the Product.Supplier property and saves the result to the database.

En zor bölümü, bağlantı URI 'sini ayrıştırır.The hardest part is parsing the link URI. Temel olarak, bu URI 'ye bir GET isteği gönderme sonucunun benzetimini yapmanız gerekir.Basically, you need to simulate the result of sending a GET request to that URI. Aşağıdaki yardımcı yöntemi bunun nasıl yapılacağını gösterir.The following helper method shows how to do this. Yöntemi, Web API yönlendirme işlemini çağırır ve ayrıştırılmış OData yolunu temsil eden bir ODataPath örneğini geri alır.The method invokes the Web API routing process and gets back an ODataPath instance that represents the parsed OData path. Bir bağlantı URI 'SI için segmentlerin biri varlık anahtarı olmalıdır.For a link URI, one of the segments should be the entity key. (Değilse, istemci hatalı bir URI gönderdi.)(If not, the client sent a bad 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;
}

Bağlantılar siliniyorDeleting Links

Bir bağlantıyı silmek için ProductsController sınıfına aşağıdaki kodu ekleyin:To delete a link, add the following code to the ProductsController class:

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

    }
}

Bu örnekte, gezinti özelliği tek bir Supplier varlıktır.In this example, the navigation property is a single Supplier entity. Gezinti özelliği bir koleksiyon ise, bir bağlantıyı silmenin URI 'SI ilgili varlık için bir anahtar içermelidir.If the navigation property is a collection, the URI to delete a link must include a key for the related entity. Örneğin:For example:

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

Bu istek, müşteri 1 ' den sipariş 1 ' i kaldırır.This request removes order 1 from customer 1. Bu durumda, DeleteLink yöntemi aşağıdaki imzaya sahip olacaktır:In this case, the DeleteLink method will have the following signature:

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

RelatedKey parametresi ilgili varlık için anahtarı verir.The relatedKey parameter gives the key for the related entity. Bu nedenle DeleteLink yönteminde, anahtar parametresine göre birincil varlığı bulun, RelatedKey parametresiyle ilgili varlığı bulun ve ardından ilişkilendirmeyi kaldırın.So in your DeleteLink method, look up the primary entity by the key parameter, find the related entity by the relatedKey parameter, and then remove the association. Veri modelinize bağlı olarak her iki DeleteLinksürümünü de uygulamanız gerekebilir.Depending on your data model, you might need to implement both versions of DeleteLink. Web API 'SI, istek URI 'sine bağlı olarak doğru sürümü çağırır.Web API will call the correct version based on the request URI.