Bagikan melalui


Hubungan Entitas di OData v4 Menggunakan ASP.NET Web API 2.2

oleh Mike Wasson

Sebagian besar himpunan data mendefinisikan hubungan antar entitas: Pelanggan memiliki pesanan; buku memiliki penulis; produk memiliki pemasok. Dengan menggunakan OData, klien dapat menavigasi melalui hubungan entitas. Mengingat produk, Anda dapat menemukan pemasok. Anda juga dapat membuat atau menghapus hubungan. Misalnya, Anda dapat mengatur pemasok untuk produk.

Tutorial ini menunjukkan cara mendukung operasi ini di OData v4 menggunakan ASP.NET Web API. Tutorial ini dibangun pada tutorial Membuat Titik Akhir OData v4 Menggunakan ASP.NET Web API 2.

Versi perangkat lunak yang digunakan dalam tutorial

  • WEB API 2.1
  • OData v4
  • Visual Studio 2017 (unduh Visual Studio 2017 di sini)
  • Entity Framework 6
  • .NET 4.5

Versi tutorial

Untuk OData Versi 3, lihat Mendukung Hubungan Entitas di OData v3.

Menambahkan Entitas Pemasok

Catatan

Tutorial ini dibangun pada tutorial Membuat Titik Akhir OData v4 Menggunakan ASP.NET Web API 2.

Pertama, kita membutuhkan entitas terkait. Tambahkan kelas bernama Supplier di folder Model.

using System.Collections.Generic;

namespace ProductService.Models
{
    public class Supplier
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public ICollection<Product> Products { get; set; }
    }
}

Tambahkan properti navigasi ke Product kelas :

using System.ComponentModel.DataAnnotations.Schema;

namespace ProductService.Models
{
    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 int? SupplierId { get; set; }
        public virtual Supplier Supplier { get; set; }
    }
}

Tambahkan DbSet baru ke ProductsContext kelas , sehingga Kerangka Kerja Entitas akan menyertakan tabel Pemasok dalam database.

public class ProductsContext : DbContext
{
    static ProductsContext()
    {
        Database.SetInitializer(new ProductInitializer());
    }

    public DbSet<Product> Products { get; set; }
    // New code:
    public DbSet<Supplier> Suppliers { get; set; }
}

Di WebApiConfig.cs, tambahkan entitas "Pemasok" yang diatur ke model data entitas:

public static void Register(HttpConfiguration config)
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<Product>("Products");
    // New code:
    builder.EntitySet<Supplier>("Suppliers");
    config.MapODataServiceRoute("ODataRoute", null, builder.GetEdmModel());
}

Menambahkan Pengontrol Pemasok

SuppliersController Tambahkan kelas ke folder Pengontrol.

using ProductService.Models;
using System.Linq;
using System.Web.OData;

namespace ProductService.Controllers
{
    public class SuppliersController : ODataController
    {
        ProductsContext db = new ProductsContext();

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Saya tidak akan menunjukkan cara menambahkan operasi CRUD untuk pengontrol ini. Langkah-langkahnya sama dengan pengontrol Produk (lihat Membuat Titik Akhir OData v4).

Untuk mendapatkan pemasok produk, klien mengirim permintaan GET:

GET /Products(1)/Supplier

Untuk mendukung permintaan ini, tambahkan metode berikut ke ProductsController kelas :

public class ProductsController : ODataController
{
    // GET /Products(1)/Supplier
    [EnableQuery]
    public SingleResult<Supplier> GetSupplier([FromODataUri] int key)
    {
        var result = db.Products.Where(m => m.Id == key).Select(m => m.Supplier);
        return SingleResult.Create(result);
    }
 
   // Other controller methods not shown.
}

Metode ini menggunakan konvensi penamaan default

  • Nama metode: GetX, di mana X adalah properti navigasi.
  • Nama parameter: kunci

Jika Anda mengikuti konvensi penamaan ini, Web API secara otomatis memetakan permintaan HTTP ke metode pengontrol.

Contoh permintaan HTTP:

GET http://myproductservice.example.com/Products(1)/Supplier HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com

Contoh respons HTTP:

HTTP/1.1 200 OK
Content-Length: 125
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 00:44:27 GMT

{
  "@odata.context":"http://myproductservice.example.com/$metadata#Suppliers/$entity","Id":2,"Name":"Wingtip Toys"
}

Dalam contoh sebelumnya, produk memiliki satu pemasok. Properti navigasi juga dapat mengembalikan koleksi. Kode berikut mendapatkan produk untuk pemasok:

public class SuppliersController : ODataController
{
    // GET /Suppliers(1)/Products
    [EnableQuery]
    public IQueryable<Product> GetProducts([FromODataUri] int key)
    {
        return db.Suppliers.Where(m => m.Id.Equals(key)).SelectMany(m => m.Products);
    }

    // Other controller methods not shown.
}

Dalam hal ini, metode mengembalikan IQueryable alih-alih T> SingleResult<

Contoh permintaan HTTP:

GET http://myproductservice.example.com/Suppliers(2)/Products HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com

Contoh respons HTTP:

HTTP/1.1 200 OK
Content-Length: 372
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 01:06:54 GMT

{
  "@odata.context":"http://myproductservice.example.com/$metadata#Products","value":[
    {
      "Id":1,"Name":"Hat","Price":14.95,"Category":"Clothing","SupplierId":2
    },{
      "Id":2,"Name":"Socks","Price":6.95,"Category":"Clothing","SupplierId":2
    },{
      "Id":4,"Name":"Pogo Stick","Price":29.99,"Category":"Toys","SupplierId":2
    }
  ]
}

Membuat Hubungan Antar Entitas

OData mendukung pembuatan atau penghapusan hubungan antara dua entitas yang ada. Dalam terminologi OData v4, hubungannya adalah "referensi". (Di OData v3, hubungan itu disebut tautan. Perbedaan protokol tidak masalah untuk tutorial ini.)

Referensi memiliki URI sendiri, dengan formulir /Entity/NavigationProperty/$ref. Misalnya, berikut adalah URI untuk mengatasi referensi antara produk dan pemasoknya:

http:/host/Products(1)/Supplier/$ref

Untuk menambahkan hubungan, klien mengirim permintaan POST atau PUT ke alamat ini.

  • PUT jika properti navigasi adalah entitas tunggal, seperti Product.Supplier.
  • POST jika properti navigasi adalah koleksi, seperti Supplier.Products.

Isi permintaan berisi URI entitas lain dalam relasi. Berikut contoh permintaan:

PUT http://myproductservice.example.com/Products(6)/Supplier/$ref HTTP/1.1
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Content-Type: application/json;odata.metadata=minimal
User-Agent: Microsoft ADO.NET Data Services
Host: myproductservice.example.com
Content-Length: 70
Expect: 100-continue

{"@odata.id":"http://myproductservice.example.com/Suppliers(4)"}

Dalam contoh ini, klien mengirim permintaan PUT ke /Products(6)/Supplier/$ref, yang merupakan $ref URI untuk Supplier produk dengan ID = 6. Jika permintaan berhasil, server mengirimkan respons 204 (Tanpa Konten):

HTTP/1.1 204 No Content
Server: Microsoft-IIS/8.0
Date: Tue, 08 Jul 2014 06:35:59 GMT

Berikut adalah metode pengontrol untuk menambahkan hubungan ke Product:

public class ProductsController : ODataController
{
    [AcceptVerbs("POST", "PUT")]
    public async Task<IHttpActionResult> CreateRef([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)
    {
        var product = await db.Products.SingleOrDefaultAsync(p => p.Id == key);
        if (product == null)
        {
            return NotFound();
        }
        switch (navigationProperty)
        {
            case "Supplier":
                // Note: The code for GetKeyFromUri is shown later in this topic.
                var relatedKey = Helpers.GetKeyFromUri<int>(Request, link);
                var supplier = await db.Suppliers.SingleOrDefaultAsync(f => f.Id == relatedKey);
                if (supplier == null)
                {
                    return NotFound();
                }

                product.Supplier = supplier;
                break;

            default:
                return StatusCode(HttpStatusCode.NotImplemented);
        }
        await db.SaveChangesAsync();
        return StatusCode(HttpStatusCode.NoContent);
    }

    // Other controller methods not shown.
}

Parameter navigationProperty menentukan hubungan mana yang akan diatur. (Jika ada lebih dari satu properti navigasi pada entitas, Anda dapat menambahkan lebih case banyak pernyataan.)

Parameter tautan berisi URI pemasok. API Web secara otomatis mengurai isi permintaan untuk mendapatkan nilai untuk parameter ini.

Untuk mencari pemasok, kita memerlukan ID (atau kunci), yang merupakan bagian dari parameter tautan . Untuk melakukan ini, gunakan metode pembantu berikut:

using Microsoft.OData.Core;
using Microsoft.OData.Core.UriParser;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;

namespace ProductService
{
    public static class Helpers
    {
        public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)
        {
            if (uri == null)
            {
                throw new ArgumentNullException("uri");
            }

            var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);

            string serviceRoot = urlHelper.CreateODataLink(
                request.ODataProperties().RouteName, 
                request.ODataProperties().PathHandler, new List<ODataPathSegment>());
            var odataPath = request.ODataProperties().PathHandler.Parse(
                request.ODataProperties().Model, 
                serviceRoot, uri.LocalPath);

            var keySegment = odataPath.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
            if (keySegment == null)
            {
                throw new InvalidOperationException("The link does not contain a key.");
            }

            var value = ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, ODataVersion.V4);
            return (TKey)value;
        }

    }
}

Pada dasarnya, metode ini menggunakan pustaka OData untuk membagi jalur URI menjadi segmen, menemukan segmen yang berisi kunci, dan mengonversi kunci menjadi jenis yang benar.

Menghapus Hubungan Antar Entitas

Untuk menghapus hubungan, klien mengirim permintaan HTTP DELETE ke URI $ref:

DELETE http://host/Products(1)/Supplier/$ref

Berikut adalah metode pengontrol untuk menghapus hubungan antara Produk dan Pemasok:

public class ProductsController : ODataController
{
    public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)
    {
        var product = db.Products.SingleOrDefault(p => p.Id == key);
        if (product == null)
        {
            return NotFound();
        }

        switch (navigationProperty)
        {
            case "Supplier":
                product.Supplier = null;
                break;

            default:
                return StatusCode(HttpStatusCode.NotImplemented);
        }
        await db.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }        

    // Other controller methods not shown.
}

Dalam hal ini, Product.Supplier adalah akhir "1" dari relasi 1-ke-banyak, sehingga Anda dapat menghapus hubungan hanya dengan mengatur Product.Supplier ke null.

Dalam akhir hubungan "banyak", klien harus menentukan entitas terkait mana yang akan dihapus. Untuk melakukannya, klien mengirim URI entitas terkait dalam string kueri permintaan. Misalnya, untuk menghapus "Produk 1" dari "Pemasok 1":

DELETE http://host/Suppliers(1)/Products/$ref?$id=http://host/Products(1)

Untuk mendukung ini di Web API, kita perlu menyertakan parameter tambahan dalam DeleteRef metode . Berikut adalah metode pengontrol untuk menghapus produk dari Supplier.Products relasi.

public class SuppliersController : ODataController
{
    public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, 
        [FromODataUri] string relatedKey, string navigationProperty)
    {
        var supplier = await db.Suppliers.SingleOrDefaultAsync(p => p.Id == key);
        if (supplier == null)
        {
            return StatusCode(HttpStatusCode.NotFound);
        }

        switch (navigationProperty)
        {
            case "Products":
                var productId = Convert.ToInt32(relatedKey);
                var product = await db.Products.SingleOrDefaultAsync(p => p.Id == productId);

                if (product == null)
                {
                    return NotFound();
                }
                product.Supplier = null;
                break;
            default:
                return StatusCode(HttpStatusCode.NotImplemented);

        }
        await db.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }

    // Other controller methods not shown.
}

Parameter kunci adalah kunci untuk pemasok, dan parameter relatedKey adalah kunci bagi produk untuk dihapus dari Products hubungan. Perhatikan bahwa Api Web secara otomatis mendapatkan kunci dari string kueri.