💥 EF Core 데이터 관계 모델링이란?

“데이터 관계를 어떤 식으로 모델링해서 EF Core에게 넘겨줄 것인가?”

조금 더 복잡한 시스템, 예를 들어 MMO 게임이나 전자상거래 플랫폼을 개발할 때는 하나의 테이블만으로는 부족합니다.
여러 개의 테이블이 서로 연결되고, 참조하며 관계를 맺는 구조가 필요합니다.


🧩 관계의 예시

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

    public ICollection<Review> Reviews { get; set; }  // 네비게이션 프로퍼티
}

public class Review
{
    public int Id { get; set; }
    public int BookId { get; set; }  // 외래 키
    public string Content { get; set; }
}
  • Book은 여러 개의 Review를 가질 수 있습니다. (1:N 관계)
  • Review는 하나의 Book만을 참조합니다.

🔑 용어 정리

용어설명
Primary Key기본 키 (고유 식별자)
Alternate Key대체 키 (ex. ISBN 등)
Principal Key외래 키가 참조하는 대상 키
Foreign Key다른 테이블을 참조하는 키
Navigation Property관계된 객체 또는 리스트를 표현
Shadow Property클래스에는 없지만 EF 내부적으로 존재하는 키

🧭 관계를 정의하는 3가지 방법

EF Core에서 관계를 정의하는 방식도 지난 시간과 마찬가지로 다음 3가지가 존재합니다.

  1. Convention (관례 기반)
  2. Data Annotation (데이터 주석 기반)
  3. Fluent API (직접 명시)

📘 Convention - 관례 기반 관계 설정

기본 규칙만 따라도 EF Core가 자동으로 관계를 인식합니다.

예:

public class Review
{
    public int Id { get; set; }
    public int BookId { get; set; } // FK
    public Book Book { get; set; }  // Navigation
}

📌 BookId 라는 이름을 보고, EF Core는 이것이 BookId를 참조하는 FK라고 인식합니다.


📝 Data Annotation - 데이터 주석 기반 관계 설정

주석으로 외래 키임을 명시할 수 있습니다.

public class Review
{
    [ForeignKey("Book")]
    public int BookId { get; set; }

    public Book Book { get; set; }
}

이렇게 명확하게 관계를 지정할 수 있습니다.


🔧 Fluent API - 직접 지정하는 방법

보다 명확하고 복잡한 관계를 정의할 수 있습니다.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Review>()
        .HasOne(r => r.Book)
        .WithMany(b => b.Reviews)
        .HasForeignKey(r => r.BookId);
}
  • HasOne : Review → Book (하나를 참조)
  • WithMany : Book → 여러 Review (1:N 관계)
  • HasForeignKey : 외래 키 지정

🧩 외래 키 & Nullable

  • 외래 키가 nullable일 경우: 관계는 Optional
  • 외래 키가 not null일 경우: 관계는 Required
public int? BookId { get; set; } // Optional
public int BookId { get; set; }  // Required

👻 Shadow Property란?

외래 키를 명시하지 않아도 EF Core가 내부적으로 생성하는 프로퍼티입니다.
예: Review 클래스에 BookId가 없지만 Book 네비게이션만 있어도 EF Core가 내부적으로 BookId를 Shadow로 추가합니다.

public Book Book { get; set; } // BookId 없음

실제 DB에는 BookId 컬럼이 생깁니다.


🔁 1:1 관계 정의

예시: 티켓 & 참석자

public class Ticket
{
    public int Id { get; set; }
    public Attendee Attendee { get; set; }
}

public class Attendee
{
    public int Id { get; set; }
    public int TicketId { get; set; }
    public Ticket Ticket { get; set; }
}

Fluent API로 설정

modelBuilder.Entity<Ticket>()
    .HasOne(t => t.Attendee)
    .WithOne(a => a.Ticket)
    .HasForeignKey<Attendee>(a => a.TicketId);

📌 외래 키 방향은 Attendee → Ticket으로 설정됨
📌 HasOneWithOne의 위치에 따라 방향이 달라짐


🔄 1:N 관계 정의

예: Book → Review

modelBuilder.Entity<Book>()
    .HasMany(b => b.Reviews)
    .WithOne(r => r.Book)
    .HasForeignKey(r => r.BookId);
  • HasMany() : Book이 여러 Review를 가짐
  • WithOne() : Review는 하나의 Book을 참조
  • HasForeignKey() : 외래 키 지정

⚙️ Fluent API 기타 설정

기능메서드
필수 여부 지정.IsRequired()
삭제 정책 설정.OnDelete(DeleteBehavior.Cascade)
무시 설정.Ignore()
제약 조건 이름 설정.HasConstraintName("FK_Review_Book")

📦 포함 관계 (Owned Type)

📌 하나의 테이블에 포함되지만 별도의 클래스로 관리하고 싶은 경우 사용

public class OrderInfo
{
    public int Id { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string City { get; set; }
    public string Zip { get; set; }
}
modelBuilder.Entity<OrderInfo>()
    .OwnsOne(o => o.Address);
  • OwnsOne() 을 통해 하나의 테이블로 병합
  • 별도 테이블 없이 포함 필드처럼 작동

🧬 TPH (Table Per Hierarchy)

📌 상속 구조를 하나의 테이블로 표현

public abstract class Payment { public int Id; }
public class PaymentCash : Payment { }
public class PaymentCard : Payment { }
  • 하나의 테이블에 Discriminator 컬럼 생성
  • 어떤 타입인지 구분 가능
  • 장점: 조회 성능 우수, 테이블 분리 안 해도 됨

profile
李家네_공부방

0개의 댓글