“데이터 관계를 어떤 식으로 모델링해서 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 내부적으로 존재하는 키 |
EF Core에서 관계를 정의하는 방식도 지난 시간과 마찬가지로 다음 3가지가 존재합니다.
기본 규칙만 따라도 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는 이것이 Book의 Id를 참조하는 FK라고 인식합니다.
주석으로 외래 키임을 명시할 수 있습니다.
public class Review
{
[ForeignKey("Book")]
public int BookId { get; set; }
public Book Book { get; set; }
}
이렇게 명확하게 관계를 지정할 수 있습니다.
보다 명확하고 복잡한 관계를 정의할 수 있습니다.
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일 경우: 관계는 Optionalnot null일 경우: 관계는 Requiredpublic int? BookId { get; set; } // Optional
public int BookId { get; set; } // Required
외래 키를 명시하지 않아도 EF Core가 내부적으로 생성하는 프로퍼티입니다.
예: Review 클래스에 BookId가 없지만 Book 네비게이션만 있어도 EF Core가 내부적으로 BookId를 Shadow로 추가합니다.
public Book Book { get; set; } // BookId 없음
실제 DB에는 BookId 컬럼이 생깁니다.
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; }
}
modelBuilder.Entity<Ticket>()
.HasOne(t => t.Attendee)
.WithOne(a => a.Ticket)
.HasForeignKey<Attendee>(a => a.TicketId);
📌 외래 키 방향은 Attendee → Ticket으로 설정됨
📌 HasOne과 WithOne의 위치에 따라 방향이 달라짐
예: Book → Review
modelBuilder.Entity<Book>()
.HasMany(b => b.Reviews)
.WithOne(r => r.Book)
.HasForeignKey(r => r.BookId);
HasMany() : Book이 여러 Review를 가짐WithOne() : Review는 하나의 Book을 참조HasForeignKey() : 외래 키 지정| 기능 | 메서드 |
|---|---|
| 필수 여부 지정 | .IsRequired() |
| 삭제 정책 설정 | .OnDelete(DeleteBehavior.Cascade) |
| 무시 설정 | .Ignore() |
| 제약 조건 이름 설정 | .HasConstraintName("FK_Review_Book") |
📌 하나의 테이블에 포함되지만 별도의 클래스로 관리하고 싶은 경우 사용
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() 을 통해 하나의 테이블로 병합📌 상속 구조를 하나의 테이블로 표현
public abstract class Payment { public int Id; }
public class PaymentCash : Payment { }
public class PaymentCard : Payment { }
Discriminator 컬럼 생성