Many-to-many Relationship

Improve EF Core performance with EF Extensions

Many-to-many Relationship

Earlier in Entity Framework, the many-to-many relationship was classified as two one-to-many relationships. To make it work the developer must create a joining entity class.

Now in Entity Framework Core 5.0, it will have full support for many-to-many relations without explicitly mapping the join table.

  • The navigation properties skip the join table and directly point to the other entity.

  • It will result in writing cleaner queries and simplify the use of the query result.

Let's consider the following model.

public class Movie
{
    public int MovieId { get; set; }
    public string Name{ get; set; }
    public Actor Actor { get; set; }
    public List<Genre> Genres { get; set; }
}

public class Genre
{
    public int GenreId { get; set; }
    public string GenreName { get; set; }
    public List<Movie> Movies{ get; set; }
}

As you can see that Movie class contains a collection of Genres, and Genre class contains a collection of Movies. EF Core 5.0 recognizes this as a many-to-many relationship by convention and there is no need for configuration in OnModelCreating.

public class MyEntityContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder opBuilder)
    {
        opBuilder.UseSqlServer("Data Source=(localdb)\\ProjectsV13;Initial Catalog=MyContextDB;");
    }

    public DbSet<Movie> Movies { get; set; }
    public DbSet<Genre> Genres { get; set; }

}

When you create migration or call the EnsureCreatedmethod, it will create the following tables including the join table.

CREATE TABLE [dbo].[Movies] (
    [MovieId] INT            IDENTITY (1, 1) NOT NULL,
    [Name]    NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Movies] PRIMARY KEY CLUSTERED ([MovieId] ASC)
);

CREATE TABLE [dbo].[Genres] (
    [GenreId]   INT            IDENTITY (1, 1) NOT NULL,
    [GenreName] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_Genres] PRIMARY KEY CLUSTERED ([GenreId] ASC)
);

CREATE TABLE [dbo].[GenreMovie] (
    [GenresGenreId] INT NOT NULL,
    [MoviesMovieId] INT NOT NULL,
    CONSTRAINT [PK_GenreMovie] PRIMARY KEY CLUSTERED ([GenresGenreId] ASC, [MoviesMovieId] ASC),
    CONSTRAINT [FK_GenreMovie_Genres_GenresGenreId] FOREIGN KEY ([GenresGenreId]) REFERENCES [dbo].[Genres] ([GenreId]) ON DELETE CASCADE,
    CONSTRAINT [FK_GenreMovie_Movies_MoviesMovieId] FOREIGN KEY ([MoviesMovieId]) REFERENCES [dbo].[Movies] ([MovieId]) ON DELETE CASCADE
);

Let's insert some movies and genres.

using (var context = new MyEntityContext())
{
    context.Database.EnsureCreated();

    var comedy = new Genre() { GenreName = "Comedy" };
    var action = new Genre() { GenreName = "Action" };
    var horror = new Genre() { GenreName = "Horror" };
    var scifi = new Genre() { GenreName = "Sci-fi" };

    context.AddRange(
        new Movie() { Name = "Avengers", Genres = new List<Genre>() { action, scifi } },
        new Movie() { Name = "Satanic Panic", Genres = new List<Genre>() { comedy, horror } });

    context.SaveChanges();

}

EF will then automatically create rows in the join table.

References

Last updated