İçeriğe atla

ASP.NET Core 6/Web Servisleri/İlişkili Veriyle Çalışma

Vikikitap, özgür kütüphane

Daha önceki derslerde Product, Supplier ve Category sınıflarının navigasyon özellikleri içerdiğini görmüştük. Bu navigasyon özellikleri Category sınıfındaki Products, Supplier sınıfındaki Products ve Product sınıfındaki Category ve Supplier özellikleridir. Şimdiye kadar bu özellikler hep null gelmişti. Şimdi bu özellikleri doldurmaya sıra geldi. Şimdi projenizin Controllers klasörüne SuppliersController.cs isimli bir controller ekleyin. İçeriği aşağıdaki gibi olsun:

using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
namespace WebApp.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class SuppliersController : ControllerBase
    {
        private DataContext context;
        public SuppliersController(DataContext ctx)
        {
            context = ctx;
        }
        [HttpGet("{id}")]
        public async Task<Supplier> GetSupplier(long id)
        {
            return await context.Suppliers.Include(s => s.Products).FirstAsync(s => s.SupplierId == id);
        }
    }
}

Bu controller veritabanından belirli bir tedarikçiyi çekmeye yaramaktadır. ProductsController sınıfındaki veritabanından veri çeken metotlardan farkı Include() metodudur. Include() metodu parametre olarak sonuca hangi özelliğin de dahil edileceğini belirten bir fonksiyon almaktadır. Bu örneğimizde ilgili tedarikçinin Products özelliğini sonuca dahil ediyoruz. Entity Framework Core veritabanındaki ilişkileri takip ederek supplier nesnesinin ilgili Products özelliğini doldurur.

Programı bu şekilde çalıştırıp api/suppliers/1 path'ına talepte bulunursanız bir çalışma zamanı hatası oluştuğunu görürsünüz. Bunun sebebi dairesel bir başvuru zinciri oluşmasıdır. Supplier sınıfının Products özelliği IEnumerable<Product> tipindedir, Product sınıfının Supplier özelliği ise Supplier tipindedir. Yani bu iki sınıf birbirine karşılıklı olarak bağımlıdır.

Aslında burada ilginç bir durum söz konusudur. Biz burada sadece ilgili supplier'ın Products özelliğinin doldurulmasını istedik. ThenInclude() metoduyla her bir ürünün Supplier'ının doldurulmasını istemedik. Aslında Entity Framework Core'un varsayılan davranışı budur, sadece istenen özellikleri doldurur, ThenInclude() metodu çağrılmadıkça özelliğin özelliğine gitmez. İşte burada veritabanına olabildiğince az sorgu gönderme amacı taşıyan Entity Framework Core'a aslında iyi niyetle eklenmiş bir özellik devreye giriyor. Entity Framework Core çektiği verideki bir navigasyon özelliğinin değeri halihazırda aynı context nesnesiyle daha önce çekilmiş veride varsa navigasyon özelliğini otomatik doldurur, ThenInclude() metodunu beklemez. Örneğimizde belirli bir Supplier'ın Product'ları çekilmiştir, bu çekilen Product'ların da Supplier'ı halihazırda çekilmiş Supplier olduğu için Product'ların da Supplier'ını ayarlamaktadır. Bu, eğer veri bellekte tutuluyorsa sorun oluşturmaz, Entity Framework Core yeterince zekidir ve ilgili Supplier'ın Product'larının Supplier olarak en baştaki Supplier'ı işaret ettiğini bilir, yeniden Supplier oluşturmaya çalışmaz. Ama JSON oluştururken sonsuz döngü oluşur. JSON'a önce supplier'ın ilk product'ı supplier'a bir alt girdi olarak girilir. Sonra bu product'ın supplier'ı product'a alt girdi olarak girilir. Sonra bu supplier'ın productları bu supplier'a alt girdi olarak girilir, derken sonsuz dallanma oluşur.

İlişkili verideki dairesel başvurudan kaçınma

[değiştir]

İlişkili verideki dairesel başvurudan kaçınmanın basit bir yolu ne yazık ki yok. Ya modeli en başından dairesel başvuru oluşturmayacak şekilde tasarlayacaksınız ya da veriyi veritabanından çektikten sonra JSON'a dönüştürmeden önce dairesel başvuru içeren özellikleri null yapacaksınız. Aşağıda ikinci yol örneklenmiştir (SuppliersController.cs):

using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
using Microsoft.EntityFrameworkCore;
namespace WebApp.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class SuppliersController : ControllerBase
    {
        private DataContext context;
        public SuppliersController(DataContext ctx)
        {
            context = ctx;
        }
        [HttpGet("{id}")]
        public async Task<Supplier?> GetSupplier(long id)
        {
            Supplier supplier = await context.Suppliers.Include(s => s.Products).FirstAsync(s => s.SupplierId == id);
            if (supplier.Products != null)
            {
                foreach (Product p in supplier.Products)
                {
                    p.Supplier = null;
                };
            }
            return supplier;
        }
    }
}

Bu örneğimizde ilgili supplier veritabanından çekildikten sonra ilgili supplier'a ait ürünler üzerinde foreach döngüsüyle dönülmekte ve her bir ürünün Supplier özelliği null yapılmaktadır.