Summary: in this tutorial, you will learn about EF Core’s many-to-many relationships and how to model them properly.
Introduction to the EF Core Many-to-many relationship
Many-to-many relationships allow you to associate multiple rows from a table with multiple rows in another table.
For example, an employee can have multiple skills while a skill is processed by multiple employees. Therefore, the relationship between employees and skills is a many-to-many relationship.
To model a many-to-many relationship between two tables, we often use a junction table and two one-to-many relationships:

In this diagram, the EmloyeeSkill
is a junction table that has two columns:
EmployeeId
– the employee’sId
that references theId
column in theEmployees
table. theEmployeeId
is a foreign key to theEmployees
table.SkillId
– the skill’sId
that references theId
column in theSkills
table. TheSkillId
column is a foreign key to theSkills
table.- Also,
EmployeeId
andSkillId
columns form the primary key of theEmployeeSkill
table. Since the primary key has two columns, it is called a composite key. It ensures that one employee possesses one skill at one time and vice versa.
In EF Core, a many-to-many relationship associates a number of entities of one entity type with any number of entities of the same or another entity type.
The easiest way to model a many-to-many relationship between two entities is to define a navigation property at both ends. For example:
public class Employee { public int Id { get; set; } public required string FirstName { get; set; } public required string LastName { get; set; } public required decimal Salary { get; set; } public required DateTime JoinedDate { get; set; } public int DepartmentId { get; set; } // Reference navigation to Department public Department Department { get; set; } = null!; // Reference navigation to EmployeeProfile public EmployeeProfile? Profile { get; set; } // collection navigation to Employee public List<Skill> Skills { get; set; } = new(); } public class Skill { public int Id { get; set; } public required string Title { get;set; } // collection navigation to Employee public List<Employee> Employees { get; set; } = new(); }
Code language: C# (cs)
In this example, the Employee
class has a collection of Skills
and the Skill
class has a collection of Employees
.
The HRContext
class defines both Employee
and Skill
DbSet
:
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace HR; public class HRContext : DbContext { public DbSet<Employee> Employees { get; set; } public DbSet<Department> Departments { get; set; } public DbSet<EmployeeProfile> EmployeeProfiles { get; set; } public DbSet<Skill> Skills { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); var connectionString = configuration.GetConnectionString("Local"); optionsBuilder.UseSqlServer(connectionString) .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information) .EnableSensitiveDataLogging(); } }
Code language: C# (cs)
By convention, EF Core will create three tables: Employees
, Skills
, and EmployeeSkill
tables.
Summary
- Use many-to-many relationships to associate any number of entities of an entity type with any number of entities of the same or another type.
- Define navigation collection in both entity types to model a many-to-many relationship.