5 min read

Entity Framework Core ile Modeller arasındaki ilişkileri tanımlama

EF Core kullanırken uygulamanın modelleri arasındaki bağlantıları tanımlamak işimizi çok kolaylaştırır.
Entity Framework Core ile Modeller arasındaki ilişkileri tanımlama

EF Core kullanırken uygulamanın modelleri arasındaki bağlantıları tanımlamak işimizi çok kolaylaştırır.

One-to-many relationship

En sık kullandığımız ilişki türüdür. Müşteri ile siparişleri, personel ile adresleri, bloglar ile blog yazıları, vb. Bir kayda karşılık ona ait olan bir çok kayıt tutulabildiği durumu ifade eder.

Örneğin bir müşteri tablosu ile Sipariş tablosunu ele alalım. Bir müşterinin zaman içinde verdiği bir çok sipariş olacaktır. Her bir sipariş kaydının hangi müşteriye ait olduğunu müşteri tablosuna işaret eden bir dış anahtarla yani foreign key ile işaretlememiz gerekir.

Tam tanımlama (Fully defined)

Bir ilişkide foreign key ile beraber navigation property'ler de tanımlanabilir. Böylece şüpheye yer bırakmayacak bir şekilde ilişki tanımlanmış olur. Örneğin bir siparişin hangi müşteriye ait olduğunu Order.CustomerId anahtarı ile işaretleyelim.

public class Customer
{
  public int Id { get;set; }
  public string Firstname { get;set; }
  public string Lastname { get;set; }
  public ICollection<Order> Orders { get;set; }
}

public class Order
{
  public int Id { get;set; }
  public decimal Total { get;set; }
  public string Details { get;set; }
  public int CustomerId { get;set; }
  public Customer Customer { get;set; }
}
One-to-many relationship tanımı. Bir müşteri bir çok sipariş verebilir. Bir sipariş ise sadece bir müşteriye aittir.

Burada tabloda yer almayan bilgilerin de olduğunu görüyoruz. Customer.Orders ve Order.Customer özelliklerinin tablolarda bir sütun olarak karşılığı yoktur. Bunlara navigation property diyoruz. Bunlar select...where şeklinde bir sorgu kullanmaya gerek kalmadan karşı tablodaki bu ilişkiye bağlı olan kayıtlara erişmemizi sağlar.

Convention'larla tanımlama

EF Core tam tanımlanma yapmasanız da belirli kurallara uyduğunuz taktirde modeller arasında one-to-many ilişkisi olduğunu anlayıp gerekeni yapacaktır. Bu kurallara "convention" denir.

One-to-One Relationships Conventions in Entity Framework Core
Learn what are the conventions in Entity Framework Core which automatically configures a one-to-one relationship.

Eğer bir one-to-many ilişkiyi navigation property'lerle tanımladıysak fakat foreign key eklemediysek, bir shadow property otomatik olarak oluşturulacaktır.

Veya sadece bir tarafta koleksiyon olan navigation property tanımladıysanız bu da yeterli olacaktır. Aynı şekilde sadece tek bir nav. property ve foreign key tanımlamak da yeterlidir.

FluentAPI ile manuel tanımlama

Bazen modeller arasındaki ilişkileri kolay müdahale edebilmek amacıyla elle tanımlamak isteriz. Bu durumda FluentAPI ile HasOne, HasMany, WithOne, WithMany metotlarından yararlanarak tanım yaparız.

Örneğin müşteri ve siparişleri arasındaki one-to-many ilişkisini FluentAPI ile tanımlayalım.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>(entity => entity.HasKey(c => c.Id));
    modelBuilder.Entity<Order>(entity => {
        entity.HasKey(o => o.Id);
        entity.HasOne(o => o.Customer)
          .WithMany(c => c.Orders)
          .HasForeignKey(k => k.CustomerId);
          //.IsRequired();
    });
}

Burada eğer müşteri numarası boş olan bir sipariş kaydı oluşturulmasını engellemek istiyorsak, IsRequired() ile bu alanın mecbur tutulmasını da sağlayabiliriz. Aksi halde sahipsiz sipariş kayıtları girilmesi mümkün olacaktır.

One-to-one relationship

Birebir ilişkili kayıtlar aslında aynı tabloda tutulabilecekken farklı iki tabloda bölünmüş durumda olan verileri ifade eder. Bu ilişki genelde veritabanı tasarımına pek bir katkı sağlamaz. Fakat özel bir durumunuz olabilir, aşırı büyümüş tabloların bölünmesi gerekebilir, veya eski bir sistemden kalmış bir veritabanı üzerinde çalışmanız gerekiyor olabilir.

Birebir ilişkide bir kaydın birden fazla eşi olamaz. Örneğin bir kişinin sadece tek bir detay kaydı bulunabilir ve bu detay kaydı sadece tek bir kişiye ait olabilir. Sipariş detaylarını ayrı bir tabloda tutmak isterseniz tek siparişin tek detay kaydı olabileceğinden bu da birebir ilişki olacaktır.

Bir müşteri ve detay bilgisini birebir ilişki şeklinde tanımlayalım.

public class Customer
{
  public int Id { get;set; }
  public string Firstname { get;set; }
  public string Lastname { get;set; }
  
  public CustomerDetails CustomerDetails { get;set; }
}

public class CustomerDetails
{
  public int Id { get;set; }
  public string Address { get;set; }
  public string Hobbies { get;set; }
  
  public string CustomerId { get;set; }
  public Customer Customer { get;set; }
}
1-to-1 ilişki tanımı. Bir müşterinin tek bir detay kaydı olabilir. Bir detay kaydı tek bir müşteriye ait olabilir.

Burada EF Core, CustomerId alanının foreign key olması gerektiğini ve bunun bir birebir ilişki olduğunu anlayacaktır. Convention'lara göre bu şekilde yaptığınız tanımlama yeterince açıktır. Bu yüzden Fluent API ile ekstra bir açıklama yapmanıza gerek kalmaz.

Eğer convention'lara uymuyorsanız, yani property isimlerinden bu ilişki net olarak anlaşılmıyorsa, o zaman Fluent API kullanarak elle açıklamanız gerekir.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>()
            .HasOne<CustomerDetails>(c => c.CustomerDetails)
            .WithOne(cd => cd.Customer)
            .HasForeignKey<CustomerDetails>(cd => cd.CustomerId);
}

Many-to-many relationship

Sık kullanılan bir bağlantı türüdür. Yalnız ekstra bir tablo oluşturarak oluşacak her bağ için buraya yeni bir kayıt atılması gerekir. EF Core bunu otomatik tanımaz. Bu yüzden bunu Fluent API ile yani elle tanımlamamız gerekir.

Bir çok ürün ve bunlarla eşleşen bir çok kategori olduğu durum buna örnektir. Makaleler ve etiketler, filmler ve oynayan oyuncular da aynı şekilde iki tarafta da çoklu kayıt ile eşleşme olabilen durumlardır.

Bir ürünün bir çok kategoriyle, bir kategorinin de bir çok ürünle eşleştiği durumu ele alalım. Bu iki nesneyi birer sınıf ile modelleyelim. Burada iki sınıfta da üçüncü tabloya işaret eden bir navigation property eklememiz gerekir. Bunların işsaret edeceği tabloyu da ayrı bir sınıf olarak yazarız. Bu üçüncü tabloda çift anahtar bulunur; her biri eşleşen tabloların birisine işaret eder.

public class Product
{
  public int Id {get;set;}
  public string Title {get;set;}
  public int ICollection<ProductCategory> ProductCategories {get;set;}
}

public class Category
{
  public int Id {get;set;}
  public string Title {get;set;}
  public ICollection<ProductCategory> ProductCategories {get;set;}
}

public class ProductCategory 
{
  public int ProductId { get; set; }
  public Product Product { get; set; }

  public int CategoryId { get; set; }
  public Category Category { get; set; }
}
Many-to-many ilişkisi. Bir ürün bir çok kategoride olabilir. Bir kategoride bir çok ürün bulunabilir.

Şimdi Fluent API ile bu üçüncü tablonun many-to-many tanımını yapalım. Önce kendi primary key'ini, sonra diğer tablolara işaret eden iki ayrı foreign key'lerini tanımlayalım.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ProductCategory>()
        .HasKey(pc => new { pc.ProductId, pc.CategoryId });  
    modelBuilder.Entity<ProductCategory>()
        .HasOne(pc => pc.Product)
        .WithMany(p => p.ProductCategories)
        .HasForeignKey(pc => pc.ProductId);  
    modelBuilder.Entity<ProductCategory>()
        .HasOne(pc => pc.Category)
        .WithMany(c => c.ProductCategories)
        .HasForeignKey(pc => pc.CategoryId);
}

Bu tanımdan sonra bir ürünün ait olduğu kategorileri getirmek için üçüncü tabloyu referans alan navigation property'den yararlanabiliriz.

// product.Categories
var categories = product.ProductCategories.Select(c => c.Category);

Convention'lar hakkında detaylı bilgi için aşağıdaki linke göz atabilirsiniz.

One-to-Many Relationships Conventions in Entity Framework Core
Learn what are the conventions in Entity Framework Core which automatically configures a one-to-many relationship.