EF Core'da (Entity Framework Core) ilişki, iki entity türünün birbirine nasıl bağlandığını tarif eder: nesne modelinde bu bağ navigation adı verilen özelliklerle, veritabanında ise foreign key (yabancı anahtar) kolonlarıyla temsil edilir. EF Core'un işi bu iki dünyayı eşlemektir: siz nesneler arasındaki referansları değiştirdiğinizde foreign key değerlerini, foreign key değerlerini değiştirdiğinizde de nesne referanslarını tutarlı hâle getirir.
Bu yazı ilişkilerin temel kavramlarını anlatıyor: principal ve dependent ayrımı, reference ve collection navigation, foreign key ile principal key, EF Core'un ilişkileri hangi kurallarla kendiliğinden keşfettiği ve bu kuralları attribute ya da Fluent API ile nasıl geçersiz kılacağınız. Her ilişki türünün ayrıntılı yapılandırması kendi yazısında: one-to-many, one-to-one ve many-to-many. Serinin tamamı için EF Core öğrenme rehberi sayfasına bakabilirsiniz.
Nesne modelinde ve veritabanında ilişki
Belgelerdeki klasik örnek bir blog ile yazılarıdır. C# tarafında Blog ve Post iki ayrı sınıftır. Bu sınıflar ilk hâlleriyle birbirinden habersizdir; ilişkiyi ortaya çıkaran şey, Post içine yayımlandığı blogu gösteren bir Blog özelliği, Blog içine de yazılarını tutan bir koleksiyon eklemektir. Önemli nokta şudur: Blog.Posts ve Post.Blog iki ayrı ilişki değil, aynı ilişkinin iki yönüdür. EF Core bu özelliklere navigation der.
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}İlişkisel veritabanında aynı bağ bir foreign key ile kurulur. Posts tablosundaki BlogId kolonu, Blogs tablosunun birincil anahtarı Id kolonunu gösterir ve bir kısıtla (constraint) korunur: BlogId içine yazılan her değer Blogs.Id içinde gerçekten bulunmak zorundadır. Bir satırın BlogId değeri 7 ise o yazı birincil anahtarı 7 olan bloga aittir. Belgelerin SQL Server için gösterdiği tablo tanımı şöyledir:
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(max) NULL,
[Content] nvarchar(max) NULL,
[PublishedOn] datetime2 NOT NULL,
[Archived] bit NOT NULL,
[BlogId] int NOT NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]) ON DELETE CASCADE);EF Core'da bir ilişkiyi eşlemek temelde üç adımdan oluşur: her entity türüne bir birincil anahtar vermek, bir tarafa foreign key özelliği eklemek ve navigation'ları bu anahtarlarla tek bir ilişki tanımında birleştirmek. Basit durumlarda EF bunu kendiliğinden yapar; isterseniz OnModelCreating içinde açıkça da yazabilirsiniz:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.HasPrincipalKey(e => e.Id);
}Temel terimler

İlişki belgeleri belirli bir terim seti kullanır. Bunları baştan ezberlemeniz gerekmez ama yapılandırma kodunu okurken hangi tarafın hangisi olduğunu bilmek hata yapmayı çok azaltır:
| Terim | Anlamı |
|---|---|
| Dependent (child) | Foreign key özelliğini taşıyan entity. Örnekte Post. |
| Principal (parent) | Birincil ya da alternatif anahtarı taşıyan entity. Örnekte Blog. |
| Principal key | Principal entity yi tekil olarak tanımlayan özellik(ler); birincil anahtar veya alternatif anahtar olabilir. |
| Foreign key | Dependent taraftaki, principal key değerini saklayan özellik(ler). |
| Reference navigation | Tek bir ilişkili entity yi gösteren özellik, örneğin Post.Blog. |
| Collection navigation | Birden çok ilişkili entity yi tutan özellik, örneğin Blog.Posts. |
| Inverse navigation | Bir navigation ın ilişkinin öbür ucundaki karşılığı. |
| Required ilişki | Foreign key nullable değildir; dependent, principal olmadan var olamaz. |
| Optional ilişki | Foreign key nullable dır; dependent bir principal e bağlı olmadan durabilir. |
| Bidirectional / unidirectional | İki tarafta da navigation var / yalnızca bir tarafta var. |
| Self-referencing | Principal ve dependent aynı entity türüdür. |
Foreign key ve principal key
Foreign key özelliği çoğunlukla konvansiyonla bulunur, ama HasForeignKey ile açıkça da belirtilebilir. Lambda kullanmak refactoring araçlarının özelliği görmesini sağlar; string ad vermek ise özellik private olduğunda, hiç var olmadığında (bu durumda bir shadow property oluşur) ya da adı model oluşturulurken hesaplandığında işe yarar. Bileşik anahtarlar için HasForeignKey(e => new { e.A, e.B }) biçimi kullanılır; principal ve foreign key'deki özelliklerin sırası birbiriyle eşleşmelidir.
Shadow foreign key, EF modelinde bulunan ama .NET sınıfında karşılığı olmayan bir özelliktir; değeri EF tarafından içeride tutulur. Foreign key'i domain modelinden gizlemek istediğinizde işe yarar. Belgeler yine de pratik bir uyarı yapar: entity'ler serileştirilip ağ üzerinden gönderilecekse foreign key değerleri ilişki bilgisinin taşınmasını kolaylaştırır; özelliği private yapmak iyi bir orta yoldur. Shadow property'lerin genel kullanımı property yapılandırma yazısında anlatılıyor.
Yanlışlıkla shadow property oluşmasını (örneğin HasForeignKey içine yazdığınız adın bir harfinin farklı olması yüzünden) engellemek isterseniz, bu uyarıyı hataya çevirebilirsiniz:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));Foreign key kısıtının adı konvansiyonla FK_<dependent tip>_<principal tip>_<foreign key özelliği> biçimindedir ve HasConstraintName ile değiştirilebilir. Bu ad EF'in çalışma zamanında kullanılmaz; yalnızca migration'larla şema oluşturulurken devreye girer. Foreign key'ler varsayılan olarak principal'ın birincil anahtarını gösterir; başka bir benzersiz özelliği hedeflemek için HasPrincipalKey kullanılır ve EF o özellik için alternatif anahtarı kendisi tanımlar. Alternatif anahtarın kendisi primary key ve composite key yazısında ele alınıyor.
Keyless entity türleri (birincil anahtarı olmayanlar) bir ilişkinin principal tarafı olamaz, çünkü foreign key'in gösterebileceği bir anahtar yoktur. Dependent tarafı olabilirler; ancak EF keyless entity'yi gösteren navigation'ları desteklemez.
EF Core ilişkileri nasıl keşfeder: konvansiyonlar

Açık yapılandırma yazmadığınızda EF önce navigation'ları arar. Bir özelliğin reference navigation sayılması için public olması, getter ve setter'ı olması (setter private veya init-only olabilir), tipinin bir referans tipi olması ve veritabanı sağlayıcısının ilkel tip olarak eşlediği ya da otomatik dönüştürdüğü bir tip olmaması gerekir. Bu yüzden int, string, Guid ve Uri navigation sayılmaz; setter'ı olmayan hesaplanmış bir özellik de sayılmaz. Collection navigation için ise public bir getter ve tipin IEnumerable<TEntity> olması yeterlidir; statik ve indexer özellikler iki durumda da dışarıda kalır.
Ardından EF her navigation için ters yönde bir eş arar. Bulursa ikisini tek bir çift yönlü ilişki yapar ve türünü navigation'ların biçimine göre belirler:
| Bir taraf | Diğer taraf | Sonuç |
|---|---|---|
| Collection | Reference | One-to-many; collection principal tarafta |
| Reference | Reference | One-to-one; dependent foreign key in bulunduğu taraf |
| Collection | Collection | Many-to-many; arada join entity |
| Tek reference (eşsiz) | yok | Tek yönlü one-to-many; reference dependent tarafta |
| Tek collection (eşsiz) | yok | Tek yönlü one-to-many; collection principal tarafta |
Eşleştirme yalnızca iki tür arasında tek bir ilişki olduğunda çalışır. Aynı iki tür arasında birden fazla ilişki varsa (örneğin bir blogun hem yazıları hem de öne çıkan tek bir yazısı) EF hangi navigation'ın hangisiyle eşleşeceğini bilemez; bunları açıkça yapılandırmanız gerekir. Aynı şekilde iki navigation aslında iki ayrı tek yönlü ilişkiyi temsil ediyorsa otomatik eşleştirme yanlış sonuç verir.
Navigation'lar belli olduktan sonra foreign key aranır. Tipi principal key ile uyumlu olan (aynı tip ya da onun nullable hâli) ve adı şu kalıplardan birine uyan özellik foreign key kabul edilir: <navigation adı><principal key adı>, <navigation adı>Id, <principal tip adı><principal key adı> ve <principal tip adı>Id. "Id" ekinin büyük/küçük harfi önemli değildir; Blogid de TheBlogID de bulunur. Foreign key bulunamazsa EF bir shadow foreign key oluşturur: tipi principal key'in tipidir ve varsayılan olarak nullable yapılır, yani ilişki optional olur. Adı, dependent tarafta navigation varsa navigation adı ile principal key adının birleşimi, yoksa principal tip adı ile principal key adının birleşimidir.
İki reference navigation eşleştiğinde foreign key yalnızca bir tarafta bulunursa o taraf dependent olur. Foreign key hiç bulunamazsa ya da iki tarafta da varsa EF dependent'ı belirleyemez ve dependent tarafın açıkça yapılandırılması gerektiğini söyleyen bir hata fırlatır. Bunun nasıl çözüleceği one-to-one yazısında anlatılıyor.
Konvansiyonlar iki önemli varsayılan daha getirir. Birincisi silme davranışıdır: required ilişkiler cascade delete ile, optional ilişkiler cascade olmadan yapılandırılır (attribute belgesine göre optional ilişkilerde varsayılan DeleteBehavior değeri ClientSetNull, required ilişkilerde Cascade). Ayrıntılar cascade delete yazısında. İkincisi indekstir: EF her foreign key için bir veritabanı indeksi oluşturur. One-to-many'de bu sıradan bir indekstir, one-to-one'da benzersiz (unique) indekstir; SQL Server'da optional one-to-one için indekse IS NOT NULL filtresi eklenir. Zaten bir indeks ya da birincil anahtarla kapsanan kolonlar için ek indeks oluşturulmaz. Bu davranışı tamamen kapatmak isterseniz konvansiyonu kaldırabilirsiniz:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}Bunu yaparsanız ihtiyaç duyduğunuz foreign key kolonlarına indeksi kendiniz eklemelisiniz; bkz. index yazısı.
Mapping attribute ile ilişki yapılandırma
Konvansiyonları sınıf üzerinde değiştirmenin yolu attribute'lardır (data annotations). İlişkiler için dört attribute öne çıkar:
- [Required]: Foreign key özelliğine ya da dependent taraftaki navigation'a konduğunda foreign key nullable olmaktan çıkar ve ilişki required olur. Foreign key shadow ise shadow özellik non-nullable yapılır. Principal taraftaki navigation'a konduğunda etkisi yoktur; nullable reference types açıkken zaten non-nullable olan bir özellikte de bir şey değiştirmez.
- [ForeignKey]: Foreign key özelliğini navigation'a bağlar. Foreign key özelliğinin üzerine navigation adıyla, ya da navigation'ın üzerine foreign key özelliğinin adıyla konabilir. Verilen ad hiçbir özelliğe uymazsa o adla bir shadow foreign key oluşturulur.
- [InverseProperty]: Bir navigation'ı tersine bağlar. Yalnızca aynı iki tür arasında birden fazla ilişki varken gerekir.
- [DeleteBehavior]: Navigation'lardan birine konarak konvansiyonel silme davranışını değiştirir, örneğin
[DeleteBehavior(DeleteBehavior.Restrict)].
[Required], [ForeignKey] ve [InverseProperty] .NET'in System.ComponentModel.DataAnnotations ad alanlarından gelir ve ek paket gerektirmez. EF Core'a özgü yeni attribute'lar (örneğin DeleteBehaviorAttribute) ise Microsoft.EntityFrameworkCore.Abstractions paketindedir; bu paket ana EF Core paketiyle birlikte gelir ama tek başına da referans verilebilecek kadar hafiftir.
Mini örnek: aynı iki tür arasında iki ilişki
Konvansiyonların tıkandığı en tipik durum, iki tür arasında birden fazla ilişki olmasıdır. Aşağıdaki modelde blogun bütün yazılarını tutan bir koleksiyon ve öne çıkan tek bir yazıyı gösteren bir referans vardır. EF, Post.Blog navigation'ının Blog.Posts ile mi yoksa Blog.FeaturedPost ile mi eşleşeceğini kendisi çıkaramaz. Belgelerdeki çözüm [InverseProperty] ile eşleşmeyi göstermektir:
public class Blog
{
public int Id { get; set; }
[InverseProperty("Blog")]
public List<Post> Posts { get; } = new();
public int FeaturedPostId { get; set; }
public Post FeaturedPost { get; set; }
}
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; init; }
}Böylece Blog.Posts ile Post.Blog tek bir one-to-many ilişki olarak eşleşir; FeaturedPost ise kendi foreign key'i FeaturedPostId ile ayrı, tek yönlü bir ilişki olarak kalır. Aynı sonucu Fluent API ile de alabilirsiniz; ilişkilerden birini HasMany(e => e.Posts).WithOne(e => e.Blog) ile açıkça tanımlamak yeterlidir.
Required, optional ve nullable reference types
Bir ilişkinin required ya da optional olmasını belirleyen şey foreign key'in nullable olup olmamasıdır. int BlogId required, int? BlogId optional bir ilişki üretir. Nullable foreign key özelliğini koruyup kolonu yine de NOT NULL yapmak isterseniz ilişkiye IsRequired() ekleyebilir ya da foreign key özelliğini Property(e => e.BlogId).IsRequired() ile işaretleyebilirsiniz.
Required ilişkinin yalnızca bir yönde garanti verdiğini bilmek önemlidir: her dependent'ın bir principal'ı olmak zorundadır, ama bir principal'ın en az bir dependent'ı olması gerekmez. Ne EF modelinde ne de ilişkisel veritabanlarında bir principal'ın belirli sayıda dependent'ı olmasını zorlamanın standart bir yolu vardır; böyle bir kural gerekiyorsa iş mantığında uygulanmalıdır. Tek istisna principal ve dependent'ın aynı tabloyu paylaştığı durumlardır (owned türler veya table splitting); orada principal'dan dependent'a giden navigation Navigation(e => e.BlogHeader).IsRequired() ile zorunlu yapılabilir. Bu konu complex types ve owned entity yazısında ayrıca geçiyor.
Sık yapılan hatalar

- Aynı ilişkiyi iki kez yapılandırmak. Bir ilişkiyi hem principal'dan (
HasMany) hem dependent'tan (HasOne) ayrı ayrı yazmak gereksizdir ve parçaları ayrı yapılandırmaya çalışmak genellikle çalışmaz. Bir uç seçip ilişkiyi bir kez yazın. - Reference navigation'ı
newile başlatmak.public Blog Blog { get; set; } = new();gibi bir başlangıç değeri, var olmayan bir blogun varmış gibi görünmesine yol açar. - Foreign key adını konvansiyona uydurmamak.
BlogRefgibi bir ad kalıplara uymaz; EF bunu sıradan bir kolon sayar ve ayrıcaBlogIdadında bir shadow foreign key oluşturur. Ya adı düzeltin ya daHasForeignKeyveya[ForeignKey]kullanın. - Tip uyuşmazlığı. Principal key
intiken foreign key'ilongyapmak uyumlu sayılmaz; konvansiyon foreign key'i bulamaz. - Collection navigation'da değer eşitliği.
Equalsmetodunu ezen entity'leri varsayılan karşılaştırıcılı birHashSetiçinde tutmak beklenmedik davranışlara yol açar;ReferenceEqualityComparerkullanın.
İlişkiler nerede kullanılır
Modelde tanımladığınız ilişkiler sorgu ve kaydetme tarafında doğrudan iş görür. İlişkili verileri üç yolla yükleyebilirsiniz: Include ile eager loading ve Load ile explicit loading Include yazısında, proxy'lerle lazy loading lazy loading yazısında anlatılıyor. Change tracker ilişkileri kullanarak navigation ile foreign key değerlerini birbirine göre düzeltir (fixup) ve SaveChanges sırasında foreign key güncellemelerini veritabanına gönderir; bkz. SaveChanges yazısı. Seed verisinde ilişkiler, birincil anahtar ile foreign key değerlerini eşleştirerek kurulur; bkz. data seeding yazısı.
Sık Sorulan Sorular
Foreign key özelliğini sınıfa eklemek zorunlu mu?
Hayır. Eklemezseniz EF bir shadow foreign key oluşturur ve ilişkiyi yalnızca navigation'lar üzerinden yönetirsiniz. Ancak entity'leri serileştirecek ya da bağlantısız senaryolarda kullanacaksanız foreign key değerini sınıfta tutmak işinizi kolaylaştırır; belgeler bunun için özelliği private yapmayı bir orta yol olarak önerir.
Konvansiyon mu, attribute mu, Fluent API mi?
Basit ve adlandırma kurallarına uyan ilişkilerde konvansiyon yeterlidir. Tek tük düzeltmeler için attribute'lar okunaklıdır. Birden fazla ilişki, alternatif anahtar, bileşik anahtar veya özel kısıt adı gibi durumlarda Fluent API kullanın; çakışma olduğunda zaten Fluent API kazanır.
Self-referencing ilişki nedir?
Principal ve dependent'ın aynı tür olduğu ilişkidir; örneğin her Employee kaydının bir yöneticisi (Manager) ve bağlı çalışanları (Reports) vardır. Aynı ilkelerle çalışır; örneği one-to-many yazısında bulabilirsiniz.
EF foreign key kolonlarına neden kendiliğinden indeks ekliyor?
İlişki üzerinden yapılan sorgular ve join'ler bu kolonları sık kullandığı için EF konvansiyonla indeks oluşturur. İndekslerin yazma maliyeti vardır; bunu istemiyorsanız ForeignKeyIndexConvention konvansiyonunu kaldırıp gereken indeksleri elle tanımlayabilirsiniz.
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 · One-to-many ilişki · One-to-one ilişki · Many-to-many ilişki · Cascade delete · Primary key ve alternate key · Include ve eager loading