Oğuzhan KARAGÜZEL

OĞUZHAN KARAGÜZEL

Yazılım geliştirici

EF Core İlişkiler, Mixed Yaklaşım ve Doğru Seçim Rehberi

Yazar: Oğuzhan KARAGÜZEL ·

Serinin önceki yazılarında DB First ve Code First yaklaşımlarını ayrı ayrı inceledik. Bu son yazıda önce Code First modelimizle ilişkili verileri nasıl ekleyip okuyacağımızı görüyor, ardından Mixed yaklaşımı anlatıyor ve üç yaklaşımı güvenlik ile veri tutarlılığı açısından karşılaştırıp hangisini ne zaman seçmeniz gerektiğini özetliyoruz.

İlişkili verileri tek seferde eklemek

Code First yazısındaki Musteri, Siparis, Urun ve Fatura modelini kullanıyoruz. İlişki türleri arasında en kapsamlısı çoka çok olduğu için örneği bu model üzerinden yapalım. (Önceki yazıda deneme amacıyla eklediğiniz IsDeleted alanını ve Calisan sınıfını modelden çıkardığınızdan emin olun; aksi halde derleme ya da migration hatası alırsınız.)

Bir müşteri, bir fatura ve bir ürün tanımlayıp bunları siparişin navigation property alanlarına (yönlendirici özellikler) ekliyoruz. Sonunda yalnızca siparişi veritabanına ekleyip kaydediyoruz:

C#
using var context = new MarketDbContext();

// Var olan müşteri: aynı context'ten okunduğu için EF Core onu "izliyor".
var musteri = context.Musteriler.First(x => x.Email == "ahmet@example.com");

var siparis = new Siparis
{
    SiparisTarihi = DateTime.UtcNow,
    Musteri = musteri,
    Fatura = new Fatura { FaturaTarihi = DateTime.UtcNow.AddDays(4) },
    Urunler = { new Urun { UrunAdi = "Hamsi", Fiyat = 150 } }
};

// Sadece siparişi ekliyoruz; bağlı yeni nesneleri EF Core kendisi bulur.
context.Siparisler.Add(siparis);
context.SaveChanges();

Perde arkasında ne oluyor?

EF Core, eklediğiniz siparişin navigation property'lerine bakar. Veritabanında henüz bulunmayan nesneleri (yeni ürün ve fatura) ekleme listesine alır ve kayıt sırasında önce bunları oluşturup Id değerlerini elde eder. Ardından bu değerleri diğer tablolardaki yabancı anahtar alanlarına yazar. Sipariş ile ürün arasındaki SiparisUrun ara tablosunu hatırlayın: bu tabloyu biz yazmadık, ama hem siparişin hem ürünün Id'si oluştuktan sonra EF Core ara tabloya gerekli satırı kendisi ekler.

Müşteri ise zaten veritabanında var ve aynı context üzerinden okunduğu için EF Core tarafından izleniyor. Bu yüzden yeniden eklenmez; yalnızca siparişin MusteriId yabancı anahtarına değeri yazılır. Hepsi tek bir SaveChanges çağrısıyla ve tek bir transaction içinde gerçekleşir; bir adım başarısız olursa hiçbir şey kaydedilmez.

Kodu çalıştırıp DBeaver'dan tabloları incelediğinizde: Faturalar tablosunda fatura satırının SiparisId ile oluştuğunu, Urunler tablosunda yeni ürünü, SiparisUrun ara tablosunda sipariş–ürün eşleşmesini ve Siparisler tablosunda müşteri Id'sinin yazıldığını görürsünüz.

İlişkili verileri okumak: Include

Aynı verileri yalnızca sipariş üzerinden okuyalım. EF Core sayesinde join yazmak zorunda kalmadan Include ile ilişkili verileri sorguya dahil edebiliriz:

C#
using var context = new MarketDbContext();

var siparis = context.Siparisler
    .AsNoTracking()
    .Include(x => x.Fatura)
    .Include(x => x.Urunler)
    .Include(x => x.Musteri)
    .OrderBy(x => x.Id)
    .First();

Console.WriteLine($"Sipariş Tarihi : {siparis.SiparisTarihi}");
Console.WriteLine($"Fatura Tarihi  : {siparis.Fatura?.FaturaTarihi}");
foreach (var urun in siparis.Urunler)
    Console.WriteLine($"Ürün : {urun.UrunAdi}  Fiyat : {urun.Fiyat}");
Console.WriteLine($"Müşteri : {siparis.Musteri.MusteriAdi}");

Birkaç not: Yalnızca okuyacağınız sorgularda AsNoTracking() değişiklik izleme maliyetini ortadan kaldırarak sorguyu hızlandırır. Birden çok koleksiyonu Include ettiğinizde sorgu büyük sonuç kümeleri üretebilir; bu durumda AsSplitQuery() ya da yalnızca ihtiyacınız olan alanları seçen bir Select projeksiyonu daha verimli olur. Ayrıca Include yazmazsanız ilişkili veriler yüklenmez (lazy loading'i özellikle açmadıysanız); bu yüzden bu örnekteki gibi her ilişkiyi açıkça belirtmek en güvenlisidir.

Mixed yaklaşım nedir?

Mixed yaklaşım, DB First ve Code First'ü birleştirir: mevcut veritabanını Scaffold-DbContext ile koda çekersiniz, ardından veritabanındaki sonraki değişiklikleri migration'larla yönetirsiniz. Mevcut yapıyı korurken yeni entity'ler ekleyebilmenizi sağlar.

Pratikte işleyiş şöyledir:

  1. Var olan veritabanını scaffold ile projeye alın (DB First yazısındaki komut).
  2. Veritabanı zaten dolu olduğu için ilk migration'ı bir "başlangıç noktası" (baseline) olarak oluşturun: Add-Migration Baseline. Oluşan migration'ın Up ve Down metotlarının içini boşaltın; böylece mevcut tabloları yeniden oluşturmaya çalışmaz.
  3. Update-Database çalıştırın. Bu adım veritabanında hiçbir şeyi değiştirmez, yalnızca migration geçmişi tablosuna başlangıç kaydını yazar.
  4. Bundan sonra Code First'te olduğu gibi entity'leri değiştirin, Add-Migration ve Update-Database ile veritabanını güncelleyin.

Önemli bir uyarı: Başlangıç noktasından sonra scaffold işlemini -Force ile tekrar çalıştırırsanız elle yaptığınız değişiklikler ezilir. Mixed yaklaşımı seçtiyseniz yönü netleştirin: bundan sonra şemanın sahibi kod mudur, yoksa veritabanı ekibi mi? İki tarafın aynı anda şemayı değiştirmesi bakım zorluğunun en büyük nedenidir.

Avantajlar ve dezavantajlar

  • Avantaj: Mevcut veritabanı yapısıyla tam uyum sağlar.
  • Avantaj: Yeni entity eklemek ve mevcut yapıyı korumak konusunda esneklik sunar.
  • Dezavantaj: İki farklı yaklaşımı bir arada kullanmanın karmaşıklığı vardır.
  • Dezavantaj: Yönetim ve bakım zorlukları (şemanın hangi taraftan değiştirileceği belirsiz kalırsa) çıkabilir.

DB First, Code First ve Mixed karşılaştırması

ÖzellikDB FirstCode FirstMixed
Kullanım kolaylığıMevcut veritabanı yapıları için kolayYeni projeler ve tam kontrol için idealKarmaşık, dikkatli yönetim gerektirir
EsneklikVeritabanı yapısına bağımlıKod üzerinden tam esneklikHem veritabanı hem kod esnekliği
YönetimScaffold işlemleriyle veritabanı değişikliklerini koda almaMigration ve update işlemleriyle kolay yönetimHem scaffold hem migration yönetimi
Güvenlik ve veri tutarlılığıVeritabanı yönetimi ön plandaKod tabanlı kontroller ve Git ile değişiklik izlemeİki yöntemin avantajlarını birleştirir

Güvenlik ve veri tutarlılığı

Hangi yaklaşımı seçerseniz seçin birkaç kural değişmez:

  • Bağlantı dizelerini güvenli yönetin. Şifreyi kaynak koda ya da Git'e koymayın; geliştirmede User Secrets veya ortam değişkenlerini, canlıda gizli bilgi yönetim servisini (ör. Azure Key Vault) kullanın.
  • En az yetki ilkesini uygulayın. Uygulamanın kullandığı veritabanı kullanıcısı sa olmamalı, yalnızca ihtiyaç duyduğu izinlere sahip olmalıdır. Şema değiştiren migration'ları ayrı, daha yetkili bir hesapla çalıştırmak iyi bir pratiktir.
  • Yedek alın. Migration ve scaffold işlemleri sırasında veri kaybını önlemek için canlı veritabanında işlem yapmadan önce yedek alın ve üretilen SQL script'ini inceleyin.

Projede veritabanını kimin yönettiği de seçimi etkiler. Veritabanıyla yalnızca geliştiriciler ilgilenecekse Code First genellikle daha güvenli bir seçimdir: her migration Git'te izlenebilir, gerekirse geri alınabilir ve veritabanının geçmişi migration sınıflarıyla takip edilebilir. Veritabanını ayrı bir uzman ekip yönetiyorsa DB First daha uygundur: izleme ve veri tutarlılığı o ekipte olduğu için geliştiricilerin şemaya müdahale etmesi gerekmez, değişiklikler scaffold ile koda yansıtılır.

Sonuç: Hangisini seçmeli?

Özetle: Veritabanı hazırsa ya da ayrı bir ekip yönetiyorsa DB First; veritabanı yoksa ve şemayı geliştiriciler yönetecekse Code First; mevcut bir veritabanını koda alıp bundan sonra kodla yönetmek istiyorsanız Mixed. Migration sınıfları veritabanı değişikliklerini izlenebilir ve güvenli kıldığı için çoğu yeni projede Code First en rahat seçimdir.

Bir not: Eski Entity Framework'te bulunan "Model First" (görsel tasarımcıyla model çizme) yaklaşımı EF Core'da desteklenmez; bu seride ele almamamızın nedeni de budur. EF Core'da pratik seçenekler yukarıdaki üç yaklaşımdır.

Seri burada bitiyor ama konu bitmiyor: yazdığınız sorguların arka planda hangi SQL'e dönüştüğünü ve nasıl hızlandırılacağını EF Core Sorgu Performansı yazısında, veritabanı tarafındaki indeksleri de SQL Server İndeksleri yazısında anlatıyorum.

Kaynaklar

Resmi dokümantasyon için Microsoft EF Core belgelerine bakabilirsiniz. EF Core ve .NET üzerine yazdıkları makalelerden faydalandığım hocalarım Gençay Yıldız (gencayyildiz.com), Burak Selim Şenyurt (buraksenyurt.com) ve Bora Kaşmer'in (borakasmer.com) sitelerinde konuyla ilgili birçok yazı bulabilirsiniz.