DEV Community

loading...

Many-to-Many relations in Entity Framework Core 3.1 and 5

ruben_j profile image Ruben Jonker ・2 min read

Many-to-Many relations are possible in Entity Framework Core but not in the way it worked in Entity Framework 6.

EF Core 3.1

Lets use the example of books and genres. Where a book can have multiple genres and a genre belongs to multiple books.

public class Book{
   public int BookId {get;set;}
   public string Name {get;set;}

   public virtual ICollection<BookGenre> BookGenres {get;set;}
}

public class Genre{
   public int GenreId {get;set;}
   public string Name {get;set;}

   public virtual ICollection<BookGenre> BookGenres {get;set;}
}

public class BookGenre{
   public int BookId {get;set;}
   public int GenreId {get;set;}

   public virtual Book Book {get;set;}
   public virtual Genre Genre {get;set;}
}

We have to define the join table and also need to use it in our navigation properties. After defining the model we can do the wire-up with the fluent api in the OnModelCreating method.

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

    modelBuilder.Entity<BookGenre>()
        .HasOne(bg => bg.Book)
        .WithMany(b => b.BookGenres)
        .HasForeignKey(bg => bg.BookId); 

    modelBuilder.Entity<BookGenre>()
        .HasOne(bg => bc.Genre)
        .WithMany(g => g.BookGenres)
        .HasForeignKey(bg => bg.GenreId);
}

Thats it. The only solution for now. It works the same as it did in EF 6 with data annotations, but it definitely isn't that elegant.

EF Core 5.0

Luckily Entity Framework Core 5.0 will have full support for many-to-many relations (It is currently the most requested feature for EF Core on GitHub). The navigation properties skip the join table and directly point to the other entity. This will result in writing cleaner queries and simplify the use of the query result.

If we project that to the previous example. The entities for Book and Genre change their navigation properties.


public class Book{
   public int BookId {get;set;}
   public string Name {get;set;}

   public virtual ICollection<Genre> Genres {get;set;}
}

public class Genre{
   public int GenreId {get;set;}
   public string Name {get;set;}

   public virtual ICollection<Book> Books {get;set;}
}

The OnModelCreating method will change a bit (this is still an EF Core 5.0 preview version, so it may differ in the version)

modelBuilder.Entity<Genre>()
                .HasMany(e => e.Books)
                .WithMany(e => e.Genres)
                .UsingEntity<BookGenre>(
                bg => bg
                    .HasOne(bg => bg.Book)
                    .WithMany()
                    .HasForeignKey("BookId"),
                bg => bg
                    .HasOne(bg => bg.Genre)
                    .WithMany()
                    .HasForeignKey("GenreId"))
                .ToTable("BookGenres")
                .HasKey(bg => new { bg.BookId, bg.GenreId });

As you can see not much changed, but the model looks more logical now and this will benefit you a lot in writing Linq queries and handling the results. For instance using ".Include" instead of Include and ThenInclude.

other links

This is a repository with a working example with the latest EF Core 5 preview packages:
https://github.com/FabioMorcillo/EF5ManyToManyExample/blob/master/ManyToManyExample/ManyToManyExample.csproj

You can find the plan for EF Core 5 here:
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/plan

Discussion

pic
Editor guide