Entity Framework

C# 닷넷 엔티티 프레임워크(Entity Framework) N:N 관계 매핑

zorimo 2025. 1. 22. 20:00

지난 포스팅에서는 Entity Framework의 1:N 관계 매핑과 제품(Product)과 카테고리(ProductCategory) 간의 관계 설정 방법을 살펴보았습니다.

이번 글에서는 N:N 관계 매핑을 학습하며, 학생(Student)강의(Course) 간의 관계를 예제로 구현하겠습니다.


1. N:N 관계란?

1:N 관계와 달리 N:N 관계는 여러 엔티티가 다른 여러 엔티티와 연결될 수 있는 관계를 의미합니다.

 

예를 들어, 한 명의 학생(Student)은 여러 강의(Course)에 등록할 수 있고 한 강의는 여러 학생에게 제공되는 경우를 볼 수 있겠죠.

 

이러한 관계를 구현하기 위해 데이터베이스에서는 중간 테이블(Join Table)을 사용합니다.


1.1 중간테이블(Join Table)

N:N 관계를 구현하려면 중간 테이블이 필요합니다. 중간 테이블은 두 테이블의 관계를 연결하는 역할을 합니다.

이 테이블은 두 엔티티의 외래 키(Foreign Key)를 포함합니다.

 

중간 테이블은 기본적으로 두 외래 키만 포함합니다.

예를 들어 StudentCourse 간의 중간 테이블은 아래와 같은 구조를 가집니다.

StudentCourses
-------------------
StudentId (FK)
CourseId  (FK)

2. N:N 관계 매핑

출처 입력

2.1 시나리오 설명

학생(Student)

- 학생은 여러 강의에 등록할 수 있습니다.

- 등록하지 않은 학생도 있을 수 있으므로 Courses 속성은 nullable로 설정합니다.

 

강의(Course)

- 강의는 여러 학생이 등록할 수 있습니다.

- 등록된 학생이 없으면 의미가 없으므로 Students 속성은 nullable하지 않고 기본값으로 초기화합니다.


2.2 모델 작성

Student 엔티 생성

namespace EFCoreDemo.Model;

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public List<Course>? Courses { get; set; } // 학생은 수업을 듣지 않을 수도 있음
}
 

Course 엔티티

namespace EFCoreDemo.Model;

public class Course
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public List<Student> Students { get; set; } = default!; // 강의는 반드시 학생과 연결
}
 

2.3 관계 매핑 코드

OnModelCreating 메서드에 N:N 관계 매핑을 추가합니다.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    (...)

    modelBuilder.Entity<Student>()
                .HasMany(s => s.Courses)    // 학생은 여러 수업을 들을 수 있음
                .WithMany(c => c.Students)  // 수업도 여러 학생을 가질 수 있음
                .UsingEntity(j => j.ToTable("StudentCourses")); // 중간 테이블 이름을 StudentCourses로 설정
}

3. 관계 매핑 적용 후 마이그레이션

3.1 DbContext 업데이트

DbSet을 추가하여 Student와 Course를 관리합니다.

public DbSet<Student> Students { get; set; } = default!;
public DbSet<Course> Courses { get; set; } = default!;
 

3.2 마이그레이션 생성 및 적용

변경 사항을 반영하기 위해 아래 명령어를 실행합니다.

dotnet ef migrations add AddStudentCourseRelation
dotnet ef database update

4. 데이터 추가 및 확인

4.1 데이터 추가

SeedDatabase_2 메서드를 생성하여 학생과 강의를 추가하고 관계를 설정합니다.

static void SeedDatabase_2()
{
    using var context = new EFCoreDemoDbContext();

    // 기존 데이터 삭제
    if (context.Students.Any())
    {
        context.Students.RemoveRange(context.Students);
        context.Courses.RemoveRange(context.Courses);
        context.SaveChanges();
        Console.WriteLine("기존 학생 및 강의 데이터가 삭제되었습니다.");
    }

    // 새로운 강의 추가
    var korean = new Course { Title = "Korean" };
    var math = new Course { Title = "Mathematics" };
    var science = new Course { Title = "Science" };
    var history = new Course { Title = "History" };

    // 새로운 학생 추가
    var student1 = new Student
    {
        Name = "Kim",
        Courses = [korean, math]
    };
    var student2 = new Student
    {
        Name = "Lee",
        Courses = [korean, science, history]
    };
    var student3 = new Student
    {
        Name = "Park",
        Courses = [math]
    };
    var student4 = new Student
    {
        Name = "Choi"
    };

    context.Students.AddRange(student1, student2, student3, student4);
    context.SaveChanges();
    Console.WriteLine("새로운 학생 및 강의 데이터가 성공적으로 추가되었습니다.");
}
 

4.2 데이터 확인

모든 학생들과 신청한 과목을 출력하는 메서드를 추가합니다.

static void ReadAllStudents()
{
    using var context = new EFCoreDemoDbContext();

    var students = context.Students
                          .Include(s => s.Courses)
                          .ToList();

    Console.WriteLine("학생 목록 및 수업 정보");
    foreach (var student in students)
    {
        Console.WriteLine($"Student: {student.Name}");

        if (student.Courses is null || student.Courses.Count == 0)
        {
            Console.WriteLine("  신청 과목 없음");
            continue;
        }
        foreach (var course in student.Courses ?? [])
        {
            Console.WriteLine($"  Course: {course.Title}");
        }
    }
}
 

4.3 결과

기존 학생 및 강의 데이터가 삭제되었습니다.
새로운 학생 및 강의 데이터가 성공적으로 추가되었습니다.
학생 목록 및 수업 정보
Student: Kim
  Course: Korean
  Course: Mathematics
Student: Lee
  Course: Korean
  Course: Science
  Course: History
Student: Park
  Course: Mathematics
Student: Choi
  신청 과목 없음

5. 마치며

이번 글에서는 N:N 관계 매핑의 개념과 구현 방법을 예제를 통해 살펴보았습니다.

이를 통해 여러 엔티티 간의 다대다(N:N) 관계를 효율적으로 처리하고,

중간 테이블을 활용해 관계를 관리하는 방법을 이해할 수 있었습니다.

 

이로써 기본적인 관계 매핑에 대한 내용은 마무리되었습니다.

 

이제 다음 포스팅부터는 보다 복잡한 관계 매핑과 성능 최적화를 위한 쿼리 작성법에 대해 알아보며,

실무에서 효율적인 데이터베이스 설계를 지원하는 방법들을 심화 학습해보겠습니다.