DEV Community

Ruben Jonker
Ruben Jonker

Posted on

7 2

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

Postgres on Neon - Get the Free Plan

No credit card required. The database you love, on a serverless platform designed to help you build faster.

Get Postgres on Neon

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!

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay