EntityFramework Core ile Many to Many (Çoka çok) İlişki
Bu gönderide ef core ile many to many yani çoka çok ilişki nasıl yapılır örneklerle beraber inceleyeceğiz.
Tanım
Çoka çok ilişkiyi örnekle açıklamak gerekirse şöyle ifade edebiliriz: her filimin birden çok oyunucusu vardır, her oyuncunun da birden çok filmi vardır.
Giriş
Aşağıdaki ef core paketlerini yükleyerek başlayalım
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Tools
İlk olarak Post ve Tag adında iki tane ana entity’mizi tanımlayalım. Her postun birden çok tagi olabilir, her tagin de birden çok postu olabilir.
Çözüm 1
Post.cs
1 2 3 4 5 6 7 8 |
public class Post { public int Id { get; set; } public string Title { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public virtual ICollection<Tag>? Tags { get; set; } } |
Tag.cs
1 2 3 4 5 6 7 |
public class Tag { public int Id { get; set; } public string Name { get; set; } = string.Empty; public virtual ICollection<Post>? Posts { get; set; } } |
Bu durumda bir migration oluşturduktan sonra ef core otomatik olarak ilişkileri oluşturacak ve PostTag adında ara tablo ekleyecek . Oluşturduğumuz bu tablo sadece veritabanında bulunacak yani class olarak erişemeyeceğiz.
Ek olarak fluent api ile de ilişkilerinizi tanımlayabilirsiniz.
Ara Tabloyu manuel olarak ekleme ve FluentApi ile ilişkilendirme (Çözüm 2)
Aşağıdaki yöntemin yukarıdakinden farkı ara tabloyu bizim oluşturmamızdır böylece kontrollerimizi ara tablo üzerinden yaparız.
PostTag.cs
1 2 3 4 5 6 7 8 |
public class PostTag { public int PostId { get; set; } public int TagId { get; set; } public virtual Post Post { get; set; } public virtual Tag Tag { get; set; } } |
Post.cs
1 2 3 4 5 6 7 8 9 |
public class Post { public int Id { get; set; } public string Title { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public virtual ICollection<PostTag> PostTags { get; set; } } |
Tag.cs
1 2 3 4 5 6 7 |
public class Tag { public int Id { get; set; } public string Name { get; set; } = string.Empty; public virtual ICollection<PostTag> PostTags { get; set; } } |
FluentApi
1 2 3 4 5 6 7 8 |
modelBuilder.Entity<PostTag>() .HasKey(pt => new { pt.PostId, pt.TagId }); modelBuilder.Entity<PostTag>().HasOne(pt => pt.Post) .WithMany(p => p.PostTags) .HasForeignKey(pt => pt.PostId); modelBuilder.Entity<PostTag>().HasOne(pt => pt.Tag) .WithMany(p => p.PostTags) .HasForeignKey(pt => pt.TagId); |
Crud işlemleri (Ekleme-Güncelleme-Silme)
Ekleme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var post = new Post { Content = "Post Content", Title = "Post Title", Tags = new List<Tag> { new Tag { Name = "Tag 1" }, new Tag { Name = "Tag 2" } } }; dbContext.Posts.Add(post); dbContext.SaveChanges(); |
Var olan Kayıda Ekleme
1 2 3 4 5 6 7 8 9 10 11 12 |
var tagIds = new int[] { 1, 2 }; //exist tags var tags = dbContext.Tags.Where(t => tagIds.Contains(t.Id)).ToList(); var post = new Post { Content = "Post Content 2", Title = "Post Title 2", Tags = tags }; dbContext.Posts.Add(post); dbContext.SaveChanges(); |
Güncelleme
Many to Many yani çoka çok ilişkilerde birden çok güncelleme senaryosu olabilir. Örneğin Post entity’sini güncelleyeceğiz ve content, title vs. geldi tagleri de bir request aracalığıyla string dizisi olarak aldık. Bu durumda şu senaryolarla karşı karşıya kalabiliriz.
- Var olan tagler yeniden gelebilir, (Havuzdan alınması lazım)
- Bütün tagler silinebilir
- Yeni tagler eklenmiş olabilir (Tag tablosuna eklenmeli)
Bu üç olasılık aynı anda olabileceği nedeniyle bütün olayları tek bir code bloğunda halledebiliriz. ilk önce güncelleyeceğimiz entitynin bütün taglerini temizliyoruz. Daha sonra gelen tagleri varsa havuzdan bulup ekliyoruz yoksa yeni bir tag oluşturup öyle ekliyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var newTags = new string[] { "Tag 1", "Tag 2", "Tag 3" }; var post = dbContext.Posts.Include(p => p.Tags).FirstOrDefault(); ArgumentNullException.ThrowIfNull(post); post.Tags.Clear(); foreach (var item in newTags) { var existItem = dbContext.Tags.FirstOrDefault(x => x.Name == item); if (existItem == null) { var tag = new Tag { Name = item }; post.Tags.Add(tag); continue; } post.Tags.Add(existItem); } dbContext.SaveChanges(); } |
Silme
Bir postun taglerini herhangi bir kısıtlama olmadan istediğiniz gibi silebilirsiniz böylece Tag tablosundan bir kayıt silersek başka bir post’un taginde de silmiş oluruz. Bu işlem CascadeDelete olarak geçer ve ilişkili alanları da kaldırır.
1 2 3 4 |
var tag = dbContext.Tags.FirstOrDefault(x => x.Name == "Tag 1"); ArgumentNullException.ThrowIfNull(tag); dbContext.Tags.Remove(tag); dbContext.SaveChanges(); |
Gönderinin sonuna geldik, okuduğunuz için teşekkürler 🙂 başka bir gönderide görüşmek üzere.