DEV Community

Muhammad Salem
Muhammad Salem

Posted on

Associations in EF Core

let's dive into a comprehensive guide on associations in Entity Framework Core (EF Core).

Associations in Entity Framework Core

In object-oriented programming and database design, associations represent relationships between entities. EF Core supports several types of associations:

  1. One-to-One (1:1)
  2. One-to-Many (1:N)
  3. Many-to-Many (M:N)

Each type of association is handled differently in EF Core. Here’s a detailed guide on how to define and work with these associations.

1. One-to-One (1:1)

In a one-to-one relationship, each entity instance is related to a single instance of another entity.

Example: A User has one Profile.

Defining One-to-One Relationship:

public class User
{
    public int UserId { get; set; }
    public string Name { get; set; }
    public Profile Profile { get; set; }
}

public class Profile
{
    public int ProfileId { get; set; }
    public string Bio { get; set; }
    public int UserId { get; set; }
    public User User { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Profile> Profiles { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .HasOne(u => u.Profile)
            .WithOne(p => p.User)
            .HasForeignKey<Profile>(p => p.UserId);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. One-to-Many (1:N)

In a one-to-many relationship, each entity instance in one entity is related to multiple instances of another entity.

Example: An Instructor can teach multiple Course instances.

Defining One-to-Many Relationship:

public class Instructor
{
    public int InstructorId { get; set; }
    public string Name { get; set; }
    public ICollection<Course> Courses { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }
    public int InstructorId { get; set; }
    public Instructor Instructor { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Instructor> Instructors { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Instructor>()
            .HasMany(i => i.Courses)
            .WithOne(c => c.Instructor)
            .HasForeignKey(c => c.InstructorId);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Many-to-Many (M:N)

In a many-to-many relationship, each entity instance is related to many instances of another entity, and vice versa.

Example: Students can enroll in multiple courses, and each course can have multiple students.

Defining Many-to-Many Relationship:

Before EF Core 5.0, you needed a join entity. From EF Core 5.0 onwards, you can directly define many-to-many relationships.

Using a Join Entity (EF Core < 5.0):

public class Student
{
    public int StudentId { get; set; }
    public string Name { get; set; }
    public ICollection<StudentCourse> StudentCourses { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }
    public ICollection<StudentCourse> StudentCourses { get; set; }
}

public class StudentCourse
{
    public int StudentId { get; set; }
    public Student Student { get; set; }
    public int CourseId { get; set; }
    public Course Course { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }
    public DbSet<StudentCourse> StudentCourses { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<StudentCourse>()
            .HasKey(sc => new { sc.StudentId, sc.CourseId });

        modelBuilder.Entity<StudentCourse>()
            .HasOne(sc => sc.Student)
            .WithMany(s => s.StudentCourses)
            .HasForeignKey(sc => sc.StudentId);

        modelBuilder.Entity<StudentCourse>()
            .HasOne(sc => sc.Course)
            .WithMany(c => c.StudentCourses)
            .HasForeignKey(sc => sc.CourseId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Directly (EF Core 5.0+):

public class Student
{
    public int StudentId { get; set; }
    public string Name { get; set; }
    public ICollection<Course> Courses { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }
    public ICollection<Student> Students { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>()
            .HasMany(s => s.Courses)
            .WithMany(c => c.Students)
            .UsingEntity<Dictionary<string, object>>(
                "StudentCourse",
                j => j.HasOne<Course>().WithMany().HasForeignKey("CourseId"),
                j => j.HasOne<Student>().WithMany().HasForeignKey("StudentId"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Additional Considerations

  1. Navigation Properties:

    • Always define navigation properties to allow EF Core to navigate between related entities.
  2. Foreign Keys:

    • Define foreign keys explicitly to ensure the integrity of the relationships.
  3. Fluent API vs Data Annotations:

    • Use the Fluent API (OnModelCreating) for complex configurations. Data annotations can be used for simpler configurations directly in the entity classes.
  4. Loading Related Data:

    • Use methods like Include and ThenInclude to load related data eagerly.
   var courseWithStudents = context.Courses
       .Include(c => c.Students)
       .ToList();
Enter fullscreen mode Exit fullscreen mode
  1. Cascade Delete:
    • Configure cascade delete behavior to ensure that related data is deleted as expected.
   modelBuilder.Entity<Course>()
       .HasMany(c => c.Students)
       .WithMany(s => s.Courses)
       .OnDelete(DeleteBehavior.Cascade);
Enter fullscreen mode Exit fullscreen mode

Example Queries

Adding Data

using (var context = new ApplicationDbContext())
{
    var instructor = new Instructor { Name = "John Doe" };
    var course = new Course { Title = "C# Basics", Description = "Learn the basics of C#", Instructor = instructor };
    var lesson = new Lesson { Title = "Introduction to C#", Content = "Content of the lesson", Duration = 1.5, Course = course };

    context.Instructors.Add(instructor);
    context.Courses.Add(course);
    context.Lessons.Add(lesson);
    context.SaveChanges();
}
Enter fullscreen mode Exit fullscreen mode

Querying Data

using (var context = new ApplicationDbContext())
{
    var courses = context.Courses
        .Include(c => c.Lessons)
        .ToList();

    var students = context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .ToList();
}
Enter fullscreen mode Exit fullscreen mode

Summary

Associations are fundamental in modeling relationships between entities in EF Core. Understanding how to properly configure one-to-one, one-to-many, and many-to-many relationships is crucial for creating a robust and efficient data model. Using a combination of navigation properties, foreign keys, the Fluent API, and eager loading will help you manage these associations effectively. Keep practicing with different scenarios to deepen your understanding of EF Core associations!

Top comments (2)

Collapse
 
ghadzhigeorgiev profile image
Georgi Hadzhigeorgiev

Nice and succinct!

Collapse
 
muhammad_salem profile image
Muhammad Salem

Thank you