MSDN Documentation

EF Core Relationships

Entity Framework Core (EF Core) provides robust support for defining and managing relationships between entities in your data model. Understanding how to configure and work with these relationships is crucial for building well-structured and efficient data access layers.

Types of Relationships

EF Core supports the following primary types of relationships:

One-to-Many Relationships

A one-to-many relationship occurs when one entity instance can be associated with multiple instances of another entity. For example, a Department can have many Employees, but each Employee belongs to only one Department.

Entity Definitions


public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }

    // Navigation property to the collection of employees
    public ICollection<Employee> Employees { get; set; }
}

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // Foreign key property
    public int DepartmentId { get; set; }

    // Navigation property to the single department
    public Department Department { get; set; }
}
                

One-to-One Relationships

A one-to-one relationship occurs when one entity instance is associated with at most one instance of another entity, and vice-versa. For example, a UserProfile might have a one-to-one relationship with a User.

Entity Definitions


public class User
{
    public int Id { get; set; }
    public string Username { get; set; }

    // Navigation property to the user profile
    public UserProfile UserProfile { get; set; }
}

public class UserProfile
{
    public int Id { get; set; }
    public string Bio { get; set; }

    // Foreign key property, also serving as the principal key in this case
    public int UserId { get; set; }

    // Navigation property to the user
    public User User { get; set; }
}
                

Many-to-Many Relationships

A many-to-many relationship occurs when one entity instance can be associated with multiple instances of another entity, and vice-versa. For example, a Student can enroll in many Courses, and a Course can have many Students. This is typically implemented using a linking/join table.

Entity Definitions


public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    // Navigation property to the courses the student is enrolled in
    public ICollection<CourseAssignment> CourseAssignments { get; set; }
}

public class Course
{
    public int Id { get; set; }
    public string Title { get; set; }

    // Navigation property to the students enrolled in this course
    public ICollection<CourseAssignment> CourseAssignments { get; set; }
}

// Linking Entity
public class CourseAssignment
{
    public int StudentId { get; set; }
    public Student Student { get; set; }

    public int CourseId { get; set; }
    public Course Course { get; set; }
}
                

Configuring Relationships

Relationships can be configured using Data Annotations or the Fluent API in your DbContext.

Using Data Annotations

Some basic relationships can be inferred by EF Core based on naming conventions (e.g., foreign keys ending in Id and navigation properties). You can explicitly define relationships using attributes like [ForeignKey] and [InverseProperty].

Example using Data Annotations


// In Employee entity:
[ForeignKey("Department")]
public int DepartmentId { get; set; }
public Department Department { get; set; }

// In Department entity:
[InverseProperty("Department")]
public ICollection<Employee> Employees { get; set; }
                

Using the Fluent API

The Fluent API offers more control and is often preferred for complex configurations. You typically configure relationships within the OnModelCreating method of your DbContext.

Example using Fluent API (DbContext)


public class ApplicationDbContext : DbContext
{
    public DbSet<Department> Departments { get; set; }
    public DbSet<Employee> Employees { get; set; }
    public DbSet<Course> Courses { get; set; }
    public DbSet<Student> Students { get; set; }
    public DbSet<CourseAssignment> CourseAssignments { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // One-to-Many: Department to Employees
        modelBuilder.Entity<Department>()
            .HasMany(d => d.Employees)
            .WithOne(e => e.Department)
            .HasForeignKey(e => e.DepartmentId);

        // One-to-One: User to UserProfile (example)
        modelBuilder.Entity<User>()
            .HasOne(u => u.UserProfile)
            .WithOne(up => up.User)
            .HasForeignKey<UserProfile>(up => up.UserId);

        // Many-to-Many: Student to Courses
        modelBuilder.Entity<CourseAssignment>()
            .HasKey(ca => new { ca.StudentId, ca.CourseId }); // Composite primary key

        modelBuilder.Entity<CourseAssignment>()
            .HasOne(ca => ca.Student)
            .WithMany(s => s.CourseAssignments)
            .HasForeignKey(ca => ca.StudentId);

        modelBuilder.Entity<CourseAssignment>()
            .HasOne(ca => ca.Course)
            .WithMany(c => c.CourseAssignments)
            .HasForeignKey(ca => ca.CourseId);

        base.OnModelCreating(modelBuilder);
    }
}
                

Loading Related Data

EF Core offers different strategies for loading related entities:

Eager Loading Example


var departmentWithEmployees = await _context.Departments
    .Include(d => d.Employees) // Eagerly load employees
    .FirstOrDefaultAsync(d => d.Id == 1);

if (departmentWithEmployees != null)
{
    Console.WriteLine($"Department: {departmentWithEmployees.Name}");
    foreach (var employee in departmentWithEmployees.Employees)
    {
        Console.WriteLine($"- {employee.FirstName} {employee.LastName}");
    }
}