Configuring Many To Many Relationships in Entity Framework Core

A many-to-many relationship occurs between entities when a one-to-many relationship between them works both ways. A book can appear in many categories and a category can contain many books. This type of relationship is represented in a database by a join table (also known among other things as a bridging, junction or linking table).

Typically, the join table contains just the entity key values of each side of the relationship. In the books and categories example, this would result in a field for the BookId and one for the CategoryId.

A many-to-many relationship is defined in code by the inclusion of collection properties in each of the entities:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
    public ICollection<Category> Categories { get; set; }
} 

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public ICollection<Book> Books { get; set; }
}

In previous versions of Entity Framework, this model definition was sufficient for EF to imply the correct type of relationship and to generate the join table for it. In EF Core 1.1.0, it is necessary to include an entity in the model to represent the join table, and then add navigation properties to either side of the many-to-many relations that point to the join entity instead:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
} 

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
}

public class BookCategory
{
    public int BookId { get; set; }
    public Book Book { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

The join table will be named after the join entity by convention. The relationship also needs to be configured via the Fluent API for EF Core 1.1.0 to be able to map it successfully:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BookCategory>()
        .HasKey(bc => new { bc.BookId, bc.CategoryId });

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Book)
        .WithMany(b => b.BookCategories)
        .HasForeignKey(bc => bc.BookId);

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Category)
        .WithMany(c => c.BookCategories)
        .HasForeignKey(bc => bc.CategoryId);
}

The primary key for the join table is a composite key comprising both of the foreign key values.

Note

The EF Team are planning on removing the need for a join entity at some point. The issue can be tracked at GitHub.


Created:
Last updated: 14/03/2017 16:33:40
Proficiency Level: Beginner