EntityFramework Core Many to Many
In this post, we going to look how to use many to many relationship at entityframewok core 6.0.
Definition
Many to many relationship to explain by example, we can express it like this; every movie has more than one actor, every actor has more than one movie well as
Intro
Let's start by installing the following ef core packages.
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Tools
Firstly, We define two entity called Post and Tag . Every post could has more than one Tag, every tag could has more than one Post.
First Solution
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; } } |
In this state, first migration will create then ef core will create table called PostTag automatically but we cant access as class to this table.
Alternatively We can define the relationship using fluent api.
Common table as manuel and FluentApi usage (Second Solution)
The difference of the method below between above is that we create the indermate table, so we can check on table.
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 operations (Create - Update - Delete)
Create
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(); |
Add to Exist Record
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(); |
Update
At Many to Many relationships could has more than one update cases. For example we update post entity and the payload are content, title etc. tags are string array. In this case, we could happen this cases:
- Exist tags could come again.
- All tags can remove.
- New tags could added. (at the tags table)
The three case can be at same time therefore all cases can be done in single code block. Firstly we're remove all tags of the entity to be updated. Then we add it if exist in tags table, otherwise we create a new tag.
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(); } |
Delete
We could remove the tags of post without any restrictions thus if we remove one record in tags table, removed from all posts associated with that tag. This operation is CascadeDelete.
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.