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’sIdthat references theIdcolumn in theEmployeestable. theEmployeeIdis a foreign key to theEmployeestable.SkillId– the skill’sIdthat references theIdcolumn in theSkillstable. TheSkillIdcolumn is a foreign key to theSkillstable.- Also,
EmployeeIdandSkillIdcolumns form the primary key of theEmployeeSkilltable. 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.