One to Many Relationships

This article does not yet include information about One-To-Many relationships. However, One-To-Many relationships have not changed since Entity Framework 6, so check out this guide for now: Relationships in Entity Framework 6

Many To Many Relationships

Many-To-Many relationships in EF Core are not quite as easy as they were in EF 6, where the join table was automatically managed for you without the need of an entity to represent it. Until they bring that functionality to EF Core (still not as of EF Core 3.1; see Issue 10508 and discussion), we have to create an entity that will handle the relationship. In fact, what we end up doing is creating one-to-many relationships between each of the two entities to be joined and the join entity.

As an example of a many-to-many relationship, a blog post can be assigned multiple tags. A post should be able to reference its tags, and a tag should be able to reference the posts to which it is assigned.

In a many-to-many relationship in a database, neither table has a foreign key to the other table. Instead, each table has a foreign key to a join table, which then pairs up the keys of the two tables. We need to create a model entity that represents that join table. To have a many-to-many relationship between Posts and Tags, we have to create an entity called PostTags which will contain a PostId and TagId, along with optional navigation properties Post and Tag. Each of the Post and Tag entities will also include a property called PostTags.

The many-to-many relationship itself has to be configured in the OnModelCreating() method of the DBContext class.

Post Entity:

public class Post {

    [Key]
    public int Id { get; set; }

    public string Title { get; set; }

    public List<PostTag> PostTags { get; set; }

    public String Content { get; set; }

}

Tag Entity:

public class Tag {

    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public List<PostTag> PostTags { get; set; }

}

PostTag Entity:

public class PostTag {

    public int PostId { get; set; }
    public Post Post { get; set; }

    public int TagId { get; set; }
    public Tag Tag { get; set; }

}

DB Context Class:

public class BlogDB : DbContext {

    public BlogDB(DbContextOptions<BlogDB> options) : base(options) {
    }

    public DbSet<Post> Posts { get; set; }

    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        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(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

Putting It To Use

Here is a very basic example where we put this new relationship to use by creating a new Post, two new Tags, and relating them using the PostTag entity.

Post post = new Post() { Title = "My Test Post" };
Tag tag1 = new Tag { Name = "test tag 1" + DateTime.Now.Second };
Tag tag2 = new Tag { Name = "test tag 2" + DateTime.Now.Second };

dbContext.Add(post);
dbContext.Add(tag1);
dbContext.Add(tag2);
dbContext.Add(new PostTag { Post = post, Tag = tag1 });
dbContext.Add(new PostTag { Post = post, Tag = tag2 });

dbContext.SaveChanges();

Select Posts with a specific Tag by Tag Id:

List<Post> PostsWithTag = dbContext.Posts.Where(p => p.PostTags.Any(pt => pt.TagId == 1)).ToList();

Select Posts with a specific Tag by Tag name:

List<Post> PostsWithTag = dbContext.Posts.Where(p => p.PostTags.Any(pt => pt.Tag.Name == "test tag 1")).ToList();

Remove a Tag from a Post by Tag Id:

dbContext.Remove(p.PostTags.Where(pt => pt.TagId == 1));

Remove a Tag from a Post by Tag Name:

dbContext.Remove(p.PostTags.Where(pt => pt.Tag.Name == "test tag 1"));
Published On: February 16, 2020Categories: Entity Framework