DEV Community

Ruben Jonker
Ruben Jonker

Posted on

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

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

Top comments (1)

Collapse
 
diadras profile image
diadras

Thanks! I was switching from SQL Server to MySQL because I run a Linux server and don't want to use SQL since this meant I had to run a heavy Docker container.

I was able to get my MtM relation working in no time with the last snippet of code!