Ç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 Tags
birçok olabilir ve her Tag
biri de herhangi bir sayıda Posts
ile 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: PostsId
tablonun birincil anahtarının yabancı anahtarı olan ve TagsId
tablonun birincil anahtarının Posts
yabancı anahtarı Tags
olan . Bu nedenle, bu tablodaki her satır bir ile bir Tag
arası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
Posts
ile 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 UsingEntity
açı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 Tag
iç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 WithMany
kullanı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 datetime
iç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ı PersonPerson
birleş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
- .NET Veri Topluluğu Standup oturumu, çoka çok'a ve altyapıya ayrıntılı bir bakış.