Çoka çok ilişkiler

Çoka çok ilişkiler, bir varlık türünün herhangi bir sayı varlığı aynı veya başka bir varlık türündeki herhangi bir sayıda varlıkla ilişkilendirildiğinde kullanılır. Örneğin, bir Post ile ilişkili Tagsbirçok olabilir ve her Tag biri de herhangi bir sayıda Postsile ilişkilendirilebilir.

Çoka çok ilişkilerini anlama

Çoka çok ilişkileri, yalnızca yabancı anahtar kullanılarak basit bir şekilde temsil edilemeyecekleri için bire çok ve bire bir ilişkilerden farklıdır. Bunun yerine, ilişkinin iki tarafını "birleştirmek" için ek bir varlık türü gerekir. Bu, "birleştirme varlık türü" olarak bilinir ve ilişkisel veritabanındaki "birleştirme tablosu" ile eşlenir. Bu birleştirme varlık türünün varlıkları, her çiftten birinin ilişkinin bir tarafındaki bir varlığa, diğerinin ise ilişkinin diğer tarafındaki bir varlığa işaret ettiği yabancı anahtar değerleri çiftleri içerir. Her birleştirme varlığı ve dolayısıyla birleştirme tablosundaki her satır, ilişkideki varlık türleri arasındaki bir ilişkiyi temsil eder.

EF Core birleştirme varlık türünü gizleyebilir ve arka planda yönetebilir. Bu, çoka çok ilişkisinin gezintilerinin doğal bir şekilde kullanılmasını ve gerektiğinde varlıkların her iki taraftan eklenmesini veya kaldırılmasını sağlar. Ancak, genel davranışlarının ve özellikle ilişkisel bir veritabanına eşlemenin anlamlı olması için arka planda neler olduğunu anlamak yararlıdır. Gönderiler ve etiketler arasındaki çoka çok ilişkiyi temsil eden bir ilişkisel veritabanı şeması kurulumuyla başlayalım:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tags" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tags" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Bu şemada birleştirme PostTag tablosu yer alır. İki sütun içerir: PostsIdtablonun birincil anahtarının yabancı anahtarı olan ve TagsIdtablonun birincil anahtarının Posts yabancı anahtarı Tags olan . Bu nedenle, bu tablodaki her satır bir ile bir Tagarasındaki Post ilişkiyi temsil eder.

EF Core'da bu şema için basit bir eşleme, her tablo için bir tane olmak üzere üç varlık türünden oluşur. Bu varlık türlerinin her biri bir .NET sınıfıyla temsil edilirse, bu sınıflar aşağıdaki gibi görünebilir:

public class Post
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

Bu eşlemede, birleştirme tablosunda tanımlanan yabancı anahtarların her biri için bir tane olmak üzere iki bire çok ilişkisi yerine çoka çok ilişkisi olduğuna dikkat edin. Bu, bu tabloları eşlemek için makul olmayan bir yol değildir, ancak birleştirme tablosunun amacını yansıtmaz; bu, iki bire çok ilişki yerine tek bir çoka çok ilişkisini temsil eder.

EF, biri Post ilgili öğesini içeren iki koleksiyon gezintisi ve ilgili Tagsöğesini içeren bir ters Tag Postsile daha doğal bir eşleme sağlar. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = [];
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = [];
    public List<Post> Posts { get; } = [];
}

public class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

Bahşiş

Bu yeni gezintiler " gezintileri atla" olarak bilinir, çünkü çoka çok ilişkinin diğer tarafına doğrudan erişim sağlamak için birleştirme varlığını atlarlar.

Aşağıdaki örneklerde gösterildiği gibi, çoka çok ilişkisi bu şekilde eşlenebilir; yani birleştirme varlığı için bir .NET sınıfıyla ve iki bire çok ilişkisi için her iki gezintiyle ve varlık türlerinde kullanıma sunulan gezintileri atlayarak. Ancak EF, birleşim varlığını saydam bir şekilde, bunun için tanımlanmış bir .NET sınıfı olmadan ve iki bire çok ilişkisi için gezintiler olmadan yönetebilir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
}

Aslında EF modeli oluşturma kuralları varsayılan olarak burada gösterilen ve Tag türlerini bu bölümün en üstündeki veritabanı şemasındaki üç tabloyla eşlerPost. Birleştirme türü açıkça kullanılmadan bu eşleme genellikle "çoka çok" terimiyle kastedilir.

Örnekler

Aşağıdaki bölümlerde, her eşlemeyi gerçekleştirmek için gereken yapılandırma da dahil olmak üzere çoka çok ilişkilerine örnekler yer almaktadır.

Bahşiş

Aşağıdaki tüm örneklerin kodu ManyToMany.cs içinde bulunabilir.

Temel çoka çok

Çoka çok için en temel örnekte, ilişkinin her bir ucundaki varlık türlerinin her ikisi de koleksiyon gezintisine sahiptir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
}

Bu ilişki kurala göre eşlenir. Gerekli olmasa da, bu ilişki için eşdeğer bir açık yapılandırma aşağıda bir öğrenme aracı olarak gösterilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts);
}

Bu açık yapılandırmada bile, ilişkinin birçok yönü kural tarafından yapılandırılmaya devam eder. Öğrenme amacıyla daha eksiksiz bir açık yapılandırma şu şekildedir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            "PostTag",
            l => l.HasOne(typeof(Tag)).WithMany().HasForeignKey("TagsId").HasPrincipalKey(nameof(Tag.Id)),
            r => r.HasOne(typeof(Post)).WithMany().HasForeignKey("PostsId").HasPrincipalKey(nameof(Post.Id)),
            j => j.HasKey("PostsId", "TagsId"));
}

Önemli

Lütfen gerekli olmasa bile her şeyi tam olarak yapılandırmayı denemeyin. Yukarıda da görülebileceği gibi kod hızla karmaşıklaşır ve hata yapmak kolaylaşır. Yukarıdaki örnekte bile modelde hala kural tarafından yapılandırılan birçok şey vardır. EF modelindeki her şeyin her zaman açıkça tamamen yapılandırılabildiğini düşünmek gerçekçi değildir.

İlişkinin kurala göre mi yoksa gösterilen açık yapılandırmalardan birini kullanarak mı oluşturulduğundan bağımsız olarak, sonuçta elde edilen eşlenmiş şema (SQLite kullanılarak) şöyle olur:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tags" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tags" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Bahşiş

Var olan bir veritabanından DbContext'in iskelesini oluşturmak için Database First akışı kullanırken, EF Core 6 ve üzeri bu deseni veritabanı şemasında arar ve bu belgede açıklandığı gibi çoka çok ilişkisi oluşturur. Bu davranış, özel bir T4 şablonu kullanarak değiştirilebilir. Diğer seçenekler için bkz . Eşlenmiş birleştirme varlıkları olmayan çoka çok ilişkiler artık yapı iskelesi oluşturulmuş.

Önemli

ŞU anda EF Core, .NET sınıfı yapılandırılmamış birleştirme varlık örneklerini temsil etmek için kullanır Dictionary<string, object> . Ancak, performansı geliştirmek için gelecekteki bir EF Core sürümünde farklı bir tür kullanılabilir. Bu açıkça yapılandırılmadığı sürece birleştirme türüne Dictionary<string, object> bağımlı olmayın.

Adlandırılmış join tablosuyla çoka çok

Önceki örnekte birleştirme tablosu kurala göre adlandırılmıştı PostTag . ile UsingEntityaçık bir ad verilebilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity("PostsToTagsJoinTable");
}

Eşlemeyle ilgili diğer her şey aynı kalır ve yalnızca birleştirme tablosunun adı değişir:

CREATE TABLE "PostsToTagsJoinTable" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostsToTagsJoinTable" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostsToTagsJoinTable_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostsToTagsJoinTable_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Birleştirme tablosu yabancı anahtar adlarıyla çoka çok

Önceki örnekten sonra birleştirme tablosundaki yabancı anahtar sütunlarının adları da değiştirilebilir. Bunu yapmanın iki yolu vardır. Birincisi, birleştirme varlığında yabancı anahtar özellik adlarını açıkça belirtmektir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasForeignKey("TagForeignKey"),
            r => r.HasOne(typeof(Post)).WithMany().HasForeignKey("PostForeignKey"));
}

İkinci yol, özellikleri kural adlarıyla bırakmak, ancak sonra bu özellikleri farklı sütun adlarıyla eşlemektir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            j =>
            {
                j.Property("PostsId").HasColumnName("PostForeignKey");
                j.Property("TagsId").HasColumnName("TagForeignKey");
            });
}

Her iki durumda da eşleme aynı kalır ve yalnızca yabancı anahtar sütun adları değiştirilir:

CREATE TABLE "PostTag" (
    "PostForeignKey" INTEGER NOT NULL,
    "TagForeignKey" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostForeignKey", "TagForeignKey"),
    CONSTRAINT "FK_PostTag_Posts_PostForeignKey" FOREIGN KEY ("PostForeignKey") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagForeignKey" FOREIGN KEY ("TagForeignKey") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Bahşiş

Burada gösterilmese de, birleştirme tablosu adını ve yabancı anahtar sütun adlarını eşlemek için önceki iki örnek birleştirilebilir.

Birleştirme varlığı için sınıf ile çoka çok

Örneklerde şu ana kadar birleştirme tablosu otomatik olarak paylaşılan türde bir varlık türüne eşlenmiştir. Bu, varlık türü için özel bir sınıfın oluşturulması gereğini ortadan kaldırır. Ancak, aşağıdaki sonraki örneklerde gösterildiği gibi, özellikle gezintiler veya yük sınıfa eklendiğinde kolayca başvurulabilmesi için böyle bir sınıfa sahip olmak yararlı olabilir. Bunu yapmak için, önce ve Tagiçin mevcut türlerine ek olarak birleştirme varlığı için Post bir tür PostTag oluşturun:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
}

Bahşiş

sınıfı herhangi bir ada sahip olabilir, ancak ilişkinin her iki ucundaki türlerin adlarını birleştirmek yaygın bir durumdur.

UsingEntity Artık yöntemi bunu ilişkinin birleştirme varlık türü olarak yapılandırmak için kullanılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>();
}

PostId veTagId, yabancı anahtarlar olarak otomatik olarak alınır ve birleştirme varlık türü için bileşik birincil anahtar olarak yapılandırılır. Yabancı anahtarlar için kullanılacak özellikler, EF kuralıyla eşleşmeyen durumlar için açıkça yapılandırılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            l => l.HasOne<Tag>().WithMany().HasForeignKey(e => e.TagId),
            r => r.HasOne<Post>().WithMany().HasForeignKey(e => e.PostId));
}

Bu örnekteki birleştirme tablosunun eşlenmiş veritabanı şeması yapısal olarak önceki örneklerle eşdeğerdir, ancak bazı farklı sütun adlarıyladır:

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Varlık birleştirme gezintileri ile çoka çok

Önceki örnekten sonra, birleştirme varlığını temsil eden bir sınıf olduğuna göre, bu sınıfa başvuran gezintileri eklemek kolaylaşır. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
}

Önemli

Bu örnekte gösterildiği gibi, birleşim varlık türüne yönelik gezintiler, çoka çok ilişkinin iki ucu arasındaki atlama gezintilerine ek olarak kullanılabilir. Bu, atlama gezintilerinin çoka çok ilişkisiyle doğal bir şekilde etkileşime geçmek için kullanılabileceğini, birleştirme varlık türüne yönelik gezintilerin ise birleştirme varlıkları üzerinde daha fazla denetime ihtiyaç duyulduğunda kullanılabileceğini gösterir. Bir anlamda, bu eşleme basit bir çoka çok eşlemesi ile veritabanı şemasıyla daha açık bir şekilde eşleşen bir eşleme arasında her iki dünyanın da en iyisini sağlar.

Birleştirme varlığındaki gezintiler kurala göre alındığından, çağrıda UsingEntity hiçbir şeyin değiştirilmesine gerek yoktur. Bu nedenle, bu örneğin yapılandırması son örnektekiyle aynıdır:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>();
}

Gezintiler, kural tarafından belirlenemeyecek durumlar için açıkça yapılandırılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            l => l.HasOne<Tag>().WithMany(e => e.PostTags),
            r => r.HasOne<Post>().WithMany(e => e.PostTags));
}

Eşlenen veritabanı şeması, modele gezintiler eklendiğinden etkilenmez:

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Birleştirme varlığında ve varlığından gezintiler içeren çoka çok

Önceki örnekte, çoka çok ilişkisinin sonundaki varlık türlerinden birleştirme varlık türüne gezintiler eklenmiştir. Gezintiler diğer yönde veya her iki yönde de eklenebilir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

Birleştirme varlığındaki gezintiler kurala göre alındığından, çağrıda UsingEntity hiçbir şeyin değiştirilmesine gerek yoktur. Bu nedenle, bu örneğin yapılandırması son örnektekiyle aynıdır:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>();
}

Gezintiler, kural tarafından belirlenemeyecek durumlar için açıkça yapılandırılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            l => l.HasOne<Tag>(e => e.Tag).WithMany(e => e.PostTags),
            r => r.HasOne<Post>(e => e.Post).WithMany(e => e.PostTags));
}

Eşlenen veritabanı şeması, modele gezintiler eklendiğinden etkilenmez:

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Gezintiler ve değiştirilen yabancı tuşlarla çoka çok

Önceki örnekte, birleştirme varlık türüne ve bu varlık türünden gezintiler içeren çoka çok gösterildi. Bu örnek aynıdır, ancak kullanılan yabancı anahtar özellikleri de değiştirilir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostForeignKey { get; set; }
    public int TagForeignKey { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

UsingEntity Bunu yapılandırmak için tekrar yöntemi kullanılır:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            l => l.HasOne<Tag>(e => e.Tag).WithMany(e => e.PostTags).HasForeignKey(e => e.TagForeignKey),
            r => r.HasOne<Post>(e => e.Post).WithMany(e => e.PostTags).HasForeignKey(e => e.PostForeignKey));
}

Eşlenen veritabanı şeması şu şekildedir:

CREATE TABLE "PostTag" (
    "PostForeignKey" INTEGER NOT NULL,
    "TagForeignKey" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostForeignKey", "TagForeignKey"),
    CONSTRAINT "FK_PostTag_Posts_PostForeignKey" FOREIGN KEY ("PostForeignKey") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagForeignKey" FOREIGN KEY ("TagForeignKey") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Tek yönlü çoka çok

Dekont

EF Core 7'de tek yönlü çoka çok ilişkileri tanıtıldı. Önceki sürümlerde geçici çözüm olarak özel gezinti kullanılabilirdi.

Çoka çok ilişkisinin her iki tarafına da gezinti eklemek gerekli değildir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
}

EF'nin bunun bire çok ilişki yerine çoka çok ilişkisi olması gerektiğini bilmesi için bazı yapılandırmalar gerekir. Bu işlem ve WithManykullanılarak HasMany yapılır, ancak gezinti olmadan kenarda bağımsız değişken geçirilmemesi gerekir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany();
}

Gezintinin kaldırılması veritabanı şemasını etkilemez:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tags" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tags" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Çoka çok ve yükü olan birleştirme tablosu

Şimdiye kadarki örneklerde birleştirme tablosu yalnızca her ilişkilendirmeyi temsil eden yabancı anahtar çiftlerini depolamak için kullanılmıştır. Bununla birlikte, ilişkilendirme hakkındaki bilgileri (örneğin, oluşturulduğu zaman) depolamak için de kullanılabilir. Böyle durumlarda, birleştirme varlığı için bir tür tanımlamak ve bu türe "ilişkilendirme yükü" özelliklerini eklemek en iyisidir. Çoka çok ilişkisi için kullanılan "gezintileri atla" özelliğine ek olarak birleştirme varlığında gezintiler oluşturmak da yaygındır. Bu ek gezintiler, birleştirme varlığına koddan kolayca başvurulmasını ve böylece yük verilerinin okunmasını ve/veya değiştirilmesini kolaylaştırır. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
    public DateTime CreatedOn { get; set; }
}

Ayrıca, oluşturulan değerleri yük özellikleri için kullanmak da yaygın bir durumdur; örneğin, ilişkilendirme satırı eklendiğinde otomatik olarak ayarlanan bir veritabanı zaman damgası. Bunun için çok az yapılandırma gerekir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            j => j.Property(e => e.CreatedOn).HasDefaultValueSql("CURRENT_TIMESTAMP"));
}

Sonuç, bir satır eklendiğinde otomatik olarak ayarlanmış bir zaman damgası ile varlık türü şemasına eşlenir:

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    "CreatedOn" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Bahşiş

Burada gösterilen SQL, SQLite içindir. SQL Server/Azure SQL'de, ve öğesini okumak datetimeiçin TEXT kullanın.HasDefaultValueSql("GETUTCDATE()").

Birleştirme varlığı olarak özel paylaşılan tür varlık türü

Önceki örnekte birleştirme varlık türü olarak türü PostTag kullanılmıştır. Bu tür, post-tags ilişkisine özgüdür. Ancak, aynı şekle sahip birden çok birleştirme tablonuz varsa, hepsi için aynı CLR türü kullanılabilir. Örneğin, tüm birleştirme tablolarımızın bir CreatedOn sütunu olduğunu düşünün. Paylaşılan tür varlık türü olarak eşlenen sınıfı kullanarak JoinType bunları eşleyebiliriz:

public class JoinType
{
    public int Id1 { get; set; }
    public int Id2 { get; set; }
    public DateTime CreatedOn { get; set; }
}

Daha sonra bu türe birden çoka çok ilişkisi tarafından birleştirme varlık türü olarak başvurulabilir. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<JoinType> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
    public List<JoinType> PostTags { get; } = [];
}

public class Blog
{
    public int Id { get; set; }
    public List<Author> Authors { get; } = [];
    public List<JoinType> BlogAuthors { get; } = [];
}

public class Author
{
    public int Id { get; set; }
    public List<Blog> Blogs { get; } = [];
    public List<JoinType> BlogAuthors { get; } = [];
}

Bu ilişkiler daha sonra birleşim türünü her ilişki için farklı bir tabloyla eşlemek üzere uygun şekilde yapılandırılabilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<JoinType>(
            "PostTag",
            l => l.HasOne<Tag>().WithMany(e => e.PostTags).HasForeignKey(e => e.Id1),
            r => r.HasOne<Post>().WithMany(e => e.PostTags).HasForeignKey(e => e.Id2),
            j => j.Property(e => e.CreatedOn).HasDefaultValueSql("CURRENT_TIMESTAMP"));

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Authors)
        .WithMany(e => e.Blogs)
        .UsingEntity<JoinType>(
            "BlogAuthor",
            l => l.HasOne<Author>().WithMany(e => e.BlogAuthors).HasForeignKey(e => e.Id1),
            r => r.HasOne<Blog>().WithMany(e => e.BlogAuthors).HasForeignKey(e => e.Id2),
            j => j.Property(e => e.CreatedOn).HasDefaultValueSql("CURRENT_TIMESTAMP"));
}

Bu, veritabanı şemasında aşağıdaki tablolarla sonuçlanır:

CREATE TABLE "BlogAuthor" (
    "Id1" INTEGER NOT NULL,
    "Id2" INTEGER NOT NULL,
    "CreatedOn" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
    CONSTRAINT "PK_BlogAuthor" PRIMARY KEY ("Id1", "Id2"),
    CONSTRAINT "FK_BlogAuthor_Authors_Id1" FOREIGN KEY ("Id1") REFERENCES "Authors" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_BlogAuthor_Blogs_Id2" FOREIGN KEY ("Id2") REFERENCES "Blogs" ("Id") ON DELETE CASCADE);


CREATE TABLE "PostTag" (
    "Id1" INTEGER NOT NULL,
    "Id2" INTEGER NOT NULL,
    "CreatedOn" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("Id1", "Id2"),
    CONSTRAINT "FK_PostTag_Posts_Id2" FOREIGN KEY ("Id2") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_Id1" FOREIGN KEY ("Id1") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Alternatif tuşlarla çoka çok

Şimdiye kadar tüm örnekler birleştirme varlık türündeki yabancı anahtarların ilişkinin her iki tarafındaki varlık türlerinin birincil anahtarlarıyla kısıtlandığını göstermiştir. Bunun yerine her yabancı anahtar veya her ikisi de alternatif bir anahtarla kısıtlanabilir. Örneğin, alternatif anahtar özelliklerine sahip olduğu buTag Post modeli göz önünde bulundurun:

public class Post
{
    public int Id { get; set; }
    public int AlternateKey { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public int AlternateKey { get; set; }
    public List<Post> Posts { get; } = [];
}

Bu modelin yapılandırması şöyledir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasPrincipalKey(nameof(Tag.AlternateKey)),
            r => r.HasOne(typeof(Post)).WithMany().HasPrincipalKey(nameof(Post.AlternateKey)));
}

Ayrıca alternatif anahtarlara sahip tablolar da dahil olmak üzere netlik sağlamak için elde edilen veritabanı şeması:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT,
    "AlternateKey" INTEGER NOT NULL,
    CONSTRAINT "AK_Posts_AlternateKey" UNIQUE ("AlternateKey"));

CREATE TABLE "Tags" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tags" PRIMARY KEY AUTOINCREMENT,
    "AlternateKey" INTEGER NOT NULL,
    CONSTRAINT "AK_Tags_AlternateKey" UNIQUE ("AlternateKey"));

CREATE TABLE "PostTag" (
    "PostsAlternateKey" INTEGER NOT NULL,
    "TagsAlternateKey" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsAlternateKey", "TagsAlternateKey"),
    CONSTRAINT "FK_PostTag_Posts_PostsAlternateKey" FOREIGN KEY ("PostsAlternateKey") REFERENCES "Posts" ("AlternateKey") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagsAlternateKey" FOREIGN KEY ("TagsAlternateKey") REFERENCES "Tags" ("AlternateKey") ON DELETE CASCADE);

Birleştirme varlık türü bir .NET türüyle temsil edilirse, alternatif anahtarları kullanma yapılandırması biraz farklıdır. Örneğin:

public class Post
{
    public int Id { get; set; }
    public int AlternateKey { get; set; }
    public List<Tag> Tags { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public int AlternateKey { get; set; }
    public List<Post> Posts { get; } = [];
    public List<PostTag> PostTags { get; } = [];
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

Yapılandırma artık genel UsingEntity<> yöntemi kullanabilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>(
            l => l.HasOne<Tag>(e => e.Tag).WithMany(e => e.PostTags).HasPrincipalKey(e => e.AlternateKey),
            r => r.HasOne<Post>(e => e.Post).WithMany(e => e.PostTags).HasPrincipalKey(e => e.AlternateKey));
}

Sonuçta elde edilen şema şu şekildedir:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT,
    "AlternateKey" INTEGER NOT NULL,
    CONSTRAINT "AK_Posts_AlternateKey" UNIQUE ("AlternateKey"));

CREATE TABLE "Tags" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tags" PRIMARY KEY AUTOINCREMENT,
    "AlternateKey" INTEGER NOT NULL,
    CONSTRAINT "AK_Tags_AlternateKey" UNIQUE ("AlternateKey"));

CREATE TABLE "PostTag" (
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostId", "TagId"),
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("AlternateKey") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("AlternateKey") ON DELETE CASCADE);

Çoka çok ve tabloyu ayrı birincil anahtarla birleştirme

Şimdiye kadar, tüm örneklerdeki birleştirme varlık türü, iki yabancı anahtar özelliğinden oluşan bir birincil anahtara sahiptir. Bunun nedeni, bu özelliklere ilişkin değerlerin her birleşiminin en fazla bir kez gerçekleşmesidir. Bu nedenle bu özellikler doğal bir birincil anahtar oluşturur.

Dekont

EF Core hiçbir koleksiyon gezintisinde yinelenen varlıkları desteklemez.

Veritabanı şemasını denetlerseniz, birleştirme tablosunun ek bir birincil anahtar sütununa sahip olması için bir neden yoktur. Bununla birlikte, var olan birleştirme tablosunda birincil anahtar sütunu tanımlanmış olabilir. EF yine de bazı yapılandırmalarla buna eşlenebilir.

Birleştirme varlığını temsil eden bir sınıf oluşturarak bunu yapmak belki de en kolayıdır. Örneğin:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
}

public class PostTag
{
    public int Id { get; set; }
    public int PostId { get; set; }
    public int TagId { get; set; }
}

Bu PostTag.Id özellik artık kurala göre birincil anahtar olarak alınır, bu nedenle gereken tek yapılandırma türü için PostTag bir çağrıdırUsingEntity:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity<PostTag>();
}

Birleştirme tablosunun elde edilen şeması ise şu şekildedir:

CREATE TABLE "PostTag" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_PostTag" PRIMARY KEY AUTOINCREMENT,
    "PostId" INTEGER NOT NULL,
    "TagId" INTEGER NOT NULL,
    CONSTRAINT "FK_PostTag_Posts_PostId" FOREIGN KEY ("PostId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagId" FOREIGN KEY ("TagId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Birincil anahtar, bir sınıf tanımlamadan birleştirme varlığına da eklenebilir. Örneğin, yalnızca Post ve Tag türleriyle:

public class Post
{
    public int Id { get; set; }
    public List<Tag> Tags { get; } = [];
}

public class Tag
{
    public int Id { get; set; }
    public List<Post> Posts { get; } = [];
}

Anahtar şu yapılandırmayla eklenebilir:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            j =>
            {
                j.IndexerProperty<int>("Id");
                j.HasKey("Id");
            });
}

Bu da ayrı bir birincil anahtar sütununa sahip birleştirme tablosuna neden olur:

CREATE TABLE "PostTag" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_PostTag" PRIMARY KEY AUTOINCREMENT,
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE CASCADE);

Art arda silme olmadan çoka çok

Yukarıda gösterilen tüm örneklerde, birleştirme tablosu ile çoka çok ilişkisinin iki tarafı arasında oluşturulan yabancı anahtarlar basamaklı silme davranışıyla oluşturulur. İlişkinin iki tarafındaki bir varlık silinirse bu varlığın birleştirme tablosundaki satırların otomatik olarak silindiği anlamına geldiği için bu çok kullanışlıdır. Veya başka bir deyişle, bir varlık artık mevcut olmadığında, diğer varlıklarla olan ilişkileri de artık yoktur.

Bu davranışı değiştirmenin ne zaman yararlı olduğunu hayal etmek zordur, ancak isterseniz yapılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().OnDelete(DeleteBehavior.Restrict),
            r => r.HasOne(typeof(Post)).WithMany().OnDelete(DeleteBehavior.Restrict));
}

Birleştirme tablosunun veritabanı şeması, yabancı anahtar kısıtlaması üzerinde kısıtlanmış silme davranışını kullanır:

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE RESTRICT,
    CONSTRAINT "FK_PostTag_Tags_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tags" ("Id") ON DELETE RESTRICT);

Çoka çoka kendi kendine başvuruyor

Aynı varlık türü, çoka çok ilişkisinin her iki ucunda da kullanılabilir; bu, "kendi kendine başvuran" ilişki olarak bilinir. Örneğin:

public class Person
{
    public int Id { get; set; }
    public List<Person> Parents { get; } = [];
    public List<Person> Children { get; } = [];
}

Bu, adlı PersonPersonbirleştirme tablosuna eşler ve her iki yabancı anahtar da tabloya People geri döner:

CREATE TABLE "PersonPerson" (
    "ChildrenId" INTEGER NOT NULL,
    "ParentsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PersonPerson" PRIMARY KEY ("ChildrenId", "ParentsId"),
    CONSTRAINT "FK_PersonPerson_People_ChildrenId" FOREIGN KEY ("ChildrenId") REFERENCES "People" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PersonPerson_People_ParentsId" FOREIGN KEY ("ParentsId") REFERENCES "People" ("Id") ON DELETE CASCADE);

Çoka çoka simetrik kendi kendine başvuran

Bazen çoka çok ilişkisi doğal olarak simetriktir. Başka bir ifadeyle, A varlığı B varlığıyla ilgiliyse, B varlığı da A varlığıyla ilgilidir. Bu, doğal olarak tek bir gezinti kullanılarak modellenmiştir. Örneğin, A kişisinin B kişisiyle arkadaş olduğu ve B kişisinin A kişisiyle arkadaş olduğu durumu düşünün:

public class Person
{
    public int Id { get; set; }
    public List<Person> Friends { get; } = [];
}

Ne yazık ki, bunu eşlemek kolay değildir. İlişkinin her iki ucu için de aynı gezinti kullanılamaz. Yapılabilecek en iyi şey, tek yönlü çoka çok ilişkisi olarak eşlemektir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasMany(e => e.Friends)
        .WithMany();
}

Ancak, iki kişinin de birbiriyle ilişkili olduğundan emin olmak için, her bir kişinin diğer kişinin Friends koleksiyonuna el ile eklenmesi gerekir. Örneğin:

ginny.Friends.Add(hermione);
hermione.Friends.Add(ginny);

Birleştirme tablosunun doğrudan kullanımı

Yukarıdaki örneklerin tümü EF Core çoka çok eşleme desenlerini kullanır. Ancak, birleştirme tablosunu normal bir varlık türüne eşlemek ve tüm işlemler için yalnızca iki bire çok ilişki kullanmak da mümkündür.

Örneğin, bu varlık türleri iki normal tablonun eşlemini temsil eder ve çoka çok ilişkileri kullanmadan tabloyu birleştirin:

public class Post
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = new();
}

public class Tag
{
    public int Id { get; set; }
    public List<PostTag> PostTags { get; } = new();
}

public class PostTag
{
    public int PostId { get; set; }
    public int TagId { get; set; }
    public Post Post { get; set; } = null!;
    public Tag Tag { get; set; } = null!;
}

Bunlar normal bire çok ilişkilerine sahip normal varlık türleri olduğundan, bu özel eşleme gerektirmez.

Ek kaynaklar