Summary: in this tutorial, you’ll learn about one-to-many relationships and how to implement them in EF Core.
Introduction to one-to-many relationships
In the relational database, a one-to-many relationship is a common type of relationship between two tables where a row in a table can associate with multiple rows in another table.
For example, a department can have multiple employees while an employee belongs to one department. Therefore, the Departments
has a one-to-many relationship with the Employees
table.
The one-to-many relationship is also known as the parent-child relationship. The Departments
is called a parent table while the Employees
table is called a child table.
To establish the one-to-many relationship between the Departments
and Employees
tables, the Employees
table needs to have a foreign key column called DepartmentId
that references the Id
column of the Employees
table:
If the DepartmentId
column accepts NULL
, you can insert a row into the Employees
table without specifying a corresponding row in the Departments
table. In this case, the employee doesn’t belong to any department.
However, if the DepartmentId
column doesn’t accept NULL
, you need to use an Id
from the Departments
table for inserting a new row into the Employees
table. In this case, an employee must belong to a specific department. In other words, you need to have at least one row in the Departments
table first before you can insert rows into the Employees
table.
We will demonstrate typical scenarios so that you can model one-to-many relationships between entities. For comprehensive scenarios, you can reference this page.
Modeling required one-to-many relationship
The following shows how to model a required one-to-many relationship between the Department
and Employee
entities:
public class Department
{
public int Id { get; set; }
public required string Name { get; set; }
// Collection navigation containing children
public ICollection<Employee> Employees { get; set; }
}
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; }
// Required foreign key property
public int DepartmentId { get; set; }
// Required reference navigation to parent
public Department Department { get; set; } = null!;
}
Code language: C# (cs)
In this example:
- The
Department
class has a property that is a collection ofEmployee
objects. - The
Employee
class has two propertiesDepartmentId
andDepartment
. TheDepartmentId
is called a foreign key property, which is marked as required. This makes the one-to-many relationship required because eachEmployee
must associate with at least aDepartment
. TheDepartment
property is known as a navigation property.
Based on this model, EF Core generates the Departments
and Employees
table in the SQL Server database with the following structure:
CREATE TABLE [dbo].[Departments] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NOT NULL,
CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE TABLE [dbo].[Employees] (CREATE TABLE [dbo].[Employees] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (MAX) NOT NULL,
[LastName] NVARCHAR (MAX) NOT NULL,
[Salary] DECIMAL (18, 2) NOT NULL,
[JoinedDate] DATETIME2 (7) NOT NULL,
[DepartmentId] INT NOT NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Employees_Departments_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [dbo].[Departments] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_Employees_DepartmentId]
ON [dbo].[Employees]([DepartmentId] ASC);
Code language: SQL (Structured Query Language) (sql)
Note that the DepartmentId
column in the Employees
table is not NULL. Also, it is a foreign key that references the Id
column of the Departments
table.
Modeling the optional one-to-many relationship
The following changes the DepartmentId
foreign key property and Departments
navigation property of the Employee
class to nullable:
public class Department
{
public int Id { get; set; }
public required string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required DateTime JoinedDate { get; set; }
public int? DepartmentId { get;set; }
public Department? Department { get; set; }
}
Code language: C# (cs)
Since the DepartmentId
and Department
properties are nullable, you can create an Employee
object without a Department
.
EF Core creates the following Departments
and Employees
tables in the database. Notice that the DepartmentId
in the Employees
table accepts NULL:
CREATE TABLE [dbo].[Departments] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NOT NULL,
CONSTRAINT [PK_Departments] PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[Employees] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (MAX) NOT NULL,
[LastName] NVARCHAR (MAX) NOT NULL,
[JoinedDate] DATETIME2 (7) NOT NULL,
[DepartmentId] INT NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Employees_Departments_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [dbo].[Departments] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Employees_DepartmentId]
ON [dbo].[Employees]([DepartmentId] ASC);
Code language: SQL (Structured Query Language) (sql)
Summary
- EF Core uses conventions to infer the one-to-many relationships between model classes and create the corresponding tables in the database.