EF Core'da one-to-many (bire çok) ilişki, bir entity'nin herhangi sayıda başka entity ile ilişkili olduğu durumdur: bir Blog birçok Post içerebilir, ama her Post yalnızca bir Blog'a aittir. Entity Framework Core'da bu ilişki principal taraftan HasMany(...).WithOne(...) ya da dependent taraftan HasOne(...).WithMany(...) ile yapılandırılır; foreign key her zaman "çok" tarafındadır.
Bu yazıda resmî belgelerdeki örnek sırasını izleyerek required ve optional ilişkiyi, shadow foreign key'i, tek yönlü (unidirectional) ilişkileri, alternatif ve bileşik anahtarları, cascade delete'i kapatmayı ve self-referencing ilişkiyi anlatıyoruz. Principal, dependent ve navigation gibi temel terimler için önce EF Core ilişkiler yazısına, serinin geri kalanı için EF Core öğrenme rehberi sayfasına bakabilirsiniz.
Required one-to-many

Belgelerdeki temel örnekte principal Blog yazılarını bir collection navigation'da tutar; dependent Post ise nullable olmayan bir BlogId foreign key'i ve bloga giden bir reference navigation taşır:
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // dependent'ları tutan collection navigation
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // required foreign key
public Blog Blog { get; set; } = null!; // principal'a giden required reference navigation
}Bir one-to-many ilişki şu parçalardan oluşur: principal taraftaki birincil veya alternatif anahtar (Blog.Id), dependent taraftaki foreign key (Post.BlogId) ve isteğe bağlı olarak iki navigation (Blog.Posts ile Post.Blog). Foreign key nullable olmadığı için ilişki required'dır: her yazının bir bloga bağlanması zorunludur.
Bu model hiç yapılandırma yazmadan konvansiyonla keşfedilir: Blog principal, Post dependent olur; Post.BlogId foreign key olarak bulunur ve nullable olmadığı için ilişki required sayılır. Konvansiyonun yetmediği durumlarda ya da niyetinizi açıkça göstermek için aynı ilişkiyi şöyle yazabilirsiniz:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}Aynı yapılandırmayı dependent taraftan başlayarak da yazabilirsiniz; belgelere göre iki yazım arasında hiçbir fark yoktur, tamamen aynı modeli üretirler:
modelBuilder.Entity<Post>()
.HasOne(e => e.Blog)
.WithMany(e => e.Posts)
.HasForeignKey(e => e.BlogId)
.IsRequired();Nullable reference types açıkken dikkat edilecek bir kural var: foreign key nullable ise reference navigation da nullable olmalıdır. Foreign key nullable değilse navigation nullable olabilir de olmayabilir de. Örnekteki = null!; yazımı derleyiciye bunun bilinçli olduğunu söyler; EF tam yüklenmiş bir ilişkide bu özelliği doldurur. Required ilişkinin yalnızca dependent tarafı bağladığını da unutmayın: hiç yazısı olmayan bir blog yine geçerlidir; "her blogun en az bir yazısı olmalı" gibi bir kural ancak iş mantığında uygulanabilir.
Optional one-to-many
Foreign key'i ve dependent'tan principal'a giden navigation'ı nullable yaptığınızda ilişki optional olur; bir yazı herhangi bir bloga bağlı olmadan da var olabilir:
public class Post
{
public int Id { get; set; }
public int? BlogId { get; set; } // optional foreign key
public Blog? Blog { get; set; } // optional reference navigation
}Bu da konvansiyonla keşfedilir. Açıkça yazmak isterseniz tek fark IsRequired(false) çağrısıdır. Optional ilişkilerde konvansiyonel silme davranışı da farklıdır: required ilişkiler cascade delete ile, optional ilişkiler cascade olmadan yapılandırılır. Bir blog silindiğinde optional ilişkideki yazıların ne olacağı cascade delete yazısında ayrıntılı olarak anlatılıyor.
Shadow foreign key ile one-to-many
Foreign key bir veritabanı ayrıntısıdır ve ilişkiyi tamamen nesne yönelimli biçimde kullanıyorsanız sınıfta görmek istemeyebilirsiniz. Post sınıfından BlogId özelliğini kaldırdığınızda EF, BlogId adında bir shadow foreign key oluşturur. Bu özelliğin nullable olup olmayacağı, nullable reference types açıksa navigation'ın nullable olmasına bağlıdır: Blog Blog required, Blog? Blog optional bir ilişki üretir (shadow özelliğin tipi sırasıyla int ve int? olur).
Nullable reference types kapalıysa durum değişir: shadow foreign key varsayılan olarak nullable oluşturulur, yani ilişki varsayılan olarak optional olur. Required istiyorsanız bunu söylemeniz gerekir:
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("BlogId")
.IsRequired();Tek yönlü (unidirectional) one-to-many

İki navigation'dan birini kaldırdığınızda ilişki tek yönlü olur. Belgeler üç varyasyon gösterir ve her birinin yapılandırmasındaki küçük ama önemli farklar şunlardır.
Principal'a navigation yok
Post içinde BlogId var ama Blog navigation'ı yoksa ilişki yine konvansiyonla bulunur. Açıkça yazarken WithOne() argümansız çağrılır; bu, EF'e o yönde navigation olmadığını söyler. Yapılandırmaya navigation'ı olmayan taraftan başlarsanız karşı tarafın tipini generic parametreyle vermelisiniz:
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.HasForeignKey(e => e.BlogId)
.IsRequired();
// Navigation'ı olmayan taraftan başlarken:
modelBuilder.Entity<Post>()
.HasOne<Blog>()
.WithMany(e => e.Posts)
.HasForeignKey(e => e.BlogId)
.IsRequired();Hem foreign key hem de dependent'taki navigation kaldırılırsa ilişki konvansiyonla optional olarak bulunur, çünkü kodda required olduğunu gösteren hiçbir şey kalmamıştır. Required istiyorsanız en azından HasMany(e => e.Posts).WithOne().IsRequired() yazmanız gerekir.
Dependent'lara navigation yok
Bu kez Blog sınıfında Posts koleksiyonu yoktur ama Post.Blog vardır. Bu da konvansiyonla keşfedilir. Açık yapılandırmada argümansız WithMany() kullanılır; navigation'ı olmayan Blog tarafından başlarsanız HasMany<Post>() yazarsınız:
modelBuilder.Entity<Post>()
.HasOne(e => e.Blog)
.WithMany()
.HasForeignKey(e => e.BlogId)
.IsRequired();Hiç navigation yok
Bazen ilişkiyi modelde tutmak ama hiç navigation açmamak isteyebilirsiniz; böyle bir ilişki yalnızca foreign key değeri değiştirilerek yönetilir. Navigation olmadığı için EF iki türün ilişkili olduğunu tahmin edemez ve ilişki konvansiyonla keşfedilmez. En kısa yapılandırma modelBuilder.Entity<Blog>().HasMany<Post>().WithOne(); biçimindedir. Bu durumda bile Post.BlogId konvansiyonla foreign key olarak bulunur ve nullable olmadığı için ilişki required olur; nullable yaparsanız optional olur.
Alternatif anahtar ve bileşik foreign key
Foreign key varsayılan olarak principal'ın birincil anahtarını gösterir ve EF konvansiyonla her zaman birincil anahtara ilişki kurar. Foreign key'i başka bir özelliğe, örneğin Blog.AlternateId'ye bağlamak isterseniz bunu HasPrincipalKey ile açıkça söylemelisiniz; o özellik böylece principal'ın alternatif anahtarı olur:
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId)
.HasForeignKey(e => e.BlogId)
.IsRequired();Principal'ın anahtarı birden çok özellikten oluşuyorsa (composite key) dependent'ın foreign key'i de aynı sayıda özellikten oluşmalıdır. İlişki konvansiyonla bulunur, ama bileşik anahtarın kendisi otomatik keşfedilmediği için HasKey(e => new { e.Id1, e.Id2 }) ile tanımlanmalıdır. Hepsini açıkça yazmak isterseniz belgelerdeki iç içe builder biçimini kullanabilirsiniz:
modelBuilder.Entity<Blog>(
nestedBuilder =>
{
nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });
nestedBuilder.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => new { e.Id1, e.Id2 })
.HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
.IsRequired();
});Cascade delete olmadan required ilişki
Konvansiyona göre required ilişkiler cascade delete ile yapılandırılır: principal silindiğinde dependent'lar da silinir, çünkü bir principal'a bağlı olmadan veritabanında var olamazlar. Bunun yerine, artık var olamayacak dependent satırları kendiliğinden silmek yerine hata alınmasını istiyorsanız silme davranışını değiştirebilirsiniz:
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.OnDelete(DeleteBehavior.Restrict);DeleteBehavior değerlerinin her birinin EF tarafında ve veritabanı tarafında ne yaptığı ayrı bir konudur; cascade delete yazısında karşılaştırmalı olarak anlatılıyor.
Self-referencing one-to-many
Principal ve dependent aynı tür olabilir. Belgelerdeki örnekte her Employee optional olarak bir yöneticiye bağlıdır ve kendisine bağlı çalışanların listesini tutar. Bu model de konvansiyonla keşfedilir:
public class Employee
{
public int Id { get; set; }
public int? ManagerId { get; set; } // optional foreign key
public Employee? Manager { get; set; } // principal'a giden navigation
public ICollection<Employee> Reports { get; } = new List<Employee>(); // dependent'lar
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOne(e => e.Manager)
.WithMany(e => e.Reports)
.HasForeignKey(e => e.ManagerId)
.IsRequired(false);
}Burada foreign key'in nullable olması mantıklıdır: en üstteki yöneticinin bir yöneticisi yoktur. Self-referencing ilişkide de diğer örneklerdeki kurallar aynen geçerlidir: foreign key nullable olduğu için navigation da nullable tanımlanmıştır ve ilişki bir kez, tek bir uçtan yapılandırılır.
Veritabanı tarafında ne oluşur?
One-to-many ilişki şemada üç iz bırakır. Birincisi foreign key kolonunun kendisidir; nullable olup olmaması ilişkinin required ya da optional olmasını yansıtır. Belgelere göre nullable bir foreign key özelliğini koruyup kolonu yine de NOT NULL yapmak mümkündür: ilişkiye IsRequired() eklemek ya da foreign key özelliğini Property(e => e.BlogId).IsRequired() ile işaretlemek aynı sonucu verir. İkincisi foreign key kısıtıdır; adı konvansiyonla FK_<dependent tip>_<principal tip>_<foreign key özelliği> kalıbındadır (bileşik foreign key'de özellik adları alt çizgiyle sıralanır) ve HasConstraintName ile değiştirilebilir. Bu ad yalnızca migration ile şema oluşturulurken kullanılır.
Üçüncüsü indekstir. EF foreign key için konvansiyonla sıradan, benzersiz olmayan bir indeks oluşturur ve required ile optional ilişkide aynı indeksi kullanır. Belgelerdeki SQL Server örnek çıktısı şöyledir:
CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);Bileşik foreign key'de indeks bütün foreign key kolonlarını kapsar. Bu indeks bir blogun yazılarını getiren sorguları destekler; kendi sorgu kalıplarınıza göre ek indeks gerekip gerekmediğini index yazısında anlatılan yöntemlerle değerlendirebilirsiniz.
Varyasyonların özeti
| Durum | Konvansiyonla bulunur mu | Gereken yapılandırma |
|---|---|---|
| FK ve iki navigation | Evet | Yok; istenirse HasMany/WithOne |
| FK nullable | Evet, optional olarak | Yok; açıkça IsRequired(false) |
| FK yok, navigation var | Evet, shadow FK ile | Required için NRT veya IsRequired |
| Principal a navigation yok | Evet | Açık yazımda WithOne() argümansız |
| FK ve dependent navigation yok | Evet, optional olarak | Required için IsRequired |
| Dependent lara navigation yok | Evet | Açık yazımda WithMany() argümansız |
| Hiç navigation yok | Hayır | HasMany<Post>().WithOne() |
| Alternatif anahtar | Hayır | HasPrincipalKey |
| Bileşik anahtar | İlişki evet, anahtar hayır | HasKey ile bileşik anahtar |
Sık yapılan hatalar

- Nullable reference types ile çelişen tanım.
int? BlogIdileBlog Blog(non-nullable) birlikte kullanılamaz; foreign key nullable ise navigation da nullable olmalıdır. - NRT kapalıyken shadow foreign key'in required sanılması. Bu durumda shadow foreign key nullable oluşur; ilişki optional'dır ve blogu olmayan yazılar kaydedilebilir.
IsRequired()ekleyin. - Navigation'ı olmayan taraftan başlarken tipi vermemek.
HasOne()yerineHasOne<Blog>(),HasMany()yerineHasMany<Post>()yazılmalıdır. - İlişkiyi iki uçtan iki kez yazmak. Bir kez, tek bir uçtan yapılandırın.
- Bileşik anahtarı tanımlamadan bileşik foreign key beklemek. Bileşik anahtarlar konvansiyonla keşfedilmez; önce
HasKeyyazılmalıdır.
İlişki kurulduktan sonra verileri birlikte okumak için Include ve eager loading, yeni yazıları bir bloga eklemek ve ilişkiyi değiştirmek için SaveChanges yazısına bakabilirsiniz. Yeni bir Post nesnesini blog.Posts koleksiyonuna eklediğinizde foreign key değerini sizin yazmanıza gerek kalmaz; EF bunu ilişki üzerinden ayarlar.
Sık Sorulan Sorular
HasMany ile mi başlamalıyım, HasOne ile mi?
Fark etmez. Blog üzerinden HasMany(...).WithOne(...) ile Post üzerinden HasOne(...).WithMany(...) belgelere göre tam olarak aynı yapılandırmayı üretir. Kodunuzda hangisi daha okunaklıysa onu seçin, ama ilişkiyi yalnızca bir kez yazın.
Foreign key yazmazsam kolon yine oluşur mu?
Evet. EF bir shadow foreign key oluşturur; adı dependent'taki navigation varsa navigation adı ile principal key adının birleşimi (örneğin BlogId), navigation yoksa principal tip adı ile principal key adının birleşimidir. Tipi principal key'in tipidir.
Bir yazıyı başka bloga taşımak için ne yapmalıyım?
Navigation'ı (post.Blog = digerBlog) ya da foreign key değerini (post.BlogId = 5) değiştirmeniz yeterlidir; change tracker diğer tarafı buna göre düzeltir ve SaveChanges güncellemeyi gönderir. Bu sürecin ayrıntıları change tracker yazısında.
Hiç yazısı olmayan bir blog required ilişkiyi bozar mı?
Hayır. Required ilişki her yazının bir blogu olmasını zorunlu kılar, ama bir blogun en az bir yazısı olmasını zorunlu kılmaz. Böyle bir kuralı ne EF modelinde ne de standart SQL'de ifade edebilirsiniz; iş mantığında kontrol etmeniz gerekir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: EF Core öğrenme rehberi · EF Core ilişkiler · One-to-one ilişki · Many-to-many ilişki · Cascade delete · Include ve eager loading