-
EF Core 테이블 상속 매핑 (TPH).NET/Database 2025. 3. 10. 22:11
TPH(Table Per Hierarchy)란?
TPH는 상속 계층을 하나의 테이블에 저장하는 방식으로, 부모 클래스와 모든 자식 클래스의 데이터를 단일 테이블에서 관리하는 전략입니다.
이를 통해 조인을 최소화하여 조회 성능을 향상시키며, EF Core는 Discriminator(구분자) 컬럼을 자동 추가하여 엔터티 유형을 구분합니다.Payment 관련 클래스 생성
namespace Database.Entity; using System; using System.ComponentModel.DataAnnotations; public abstract class Payment { [Key] public int Id { get; set; } public decimal Amount { get; set; } // 결제 금액 public DateTimeOffset PaymentDate { get; set; } = DateTimeOffset.UtcNow; // UTC 0 기준 저장 [MaxLength(20)] public string Status { get; set; } = "Pending"; // "Pending", "Completed", "Failed" } public class CreditCardPayment : Payment { [MaxLength(20)] public string CardNumber { get; set; } = string.Empty; // 마스킹된 카드번호 (예: ****-****-****-1234) [MaxLength(50)] public string CardHolder { get; set; } = string.Empty; // 카드 소유자 이름 [MaxLength(5)] public string ExpirationDate { get; set; } = string.Empty; // 유효기간 MM/YY } public class PayPalPayment : Payment { [MaxLength(100)] public string PayPalEmail { get; set; } = string.Empty; // 페이팔 계정 이메일 } public class BankTransferPayment : Payment { [MaxLength(30)] public string BankAccountNumber { get; set; } = string.Empty; // 가상 계좌번호 [MaxLength(50)] public string BankName { get; set; } = string.Empty; // 은행명 }
using Microsoft.EntityFrameworkCore; using Database.Entity; namespace Database.Context; public partial class ShopDbContext : DbContext { #region TPH public DbSet<Payment> Payments { get; set; } #endregion public ShopDbContext(DbContextOptions<ShopDbContext> options) :base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Payment>() .UseTphMappingStrategy() .HasDiscriminator<string>("PaymentType") // Discriminator 컬럼 이름 변경 .HasValue<CreditCardPayment>("CreditCard") .HasValue<PayPalPayment>("PayPal") .HasValue<BankTransferPayment>("BankTransfer"); } }
DbContext에서 Discriminator 컬럼 설정
EF Core에서 UseTphMappingStrategy()를 사용하여 TPH 전략을 적용하며,
.HasDiscriminator<string>("PaymentType")을 통해 Discriminator 컬럼의 이름을 "PaymentType"으로 지정합니다.각 서브 클래스(CreditCardPayment, PayPalPayment, BankTransferPayment)는
.HasValue<T>("값")을 통해 고유한 값을 갖도록 설정됩니다.이렇게 하면 Payments 테이블에 PaymentType 컬럼이 추가되며,
각 행이 어떤 결제 타입인지 구분할 수 있도록 저장됩니다.테이블 구조: Payments 하나만 생성
이 방식에서는 CreditCardPayment, PayPalPayment, BankTransferPayment 등의 개별 테이블이 생성되지 않습니다.
대신, 하나의 Payments 테이블만 존재하며,
모든 결제 관련 데이터가 공통 컬럼 + 개별 타입별 컬럼 형태로 저장됩니다.데이터 사용
//데이터 추가 context.Payments.AddAsync(new CreditCardPayment { Amount = 3001, PaymentDate = DateTimeOffset.UtcNow, Status = "Completed", CardNumber = "0000-0000-0000-1234", CardHolder = "John Doe", ExpirationDate = "12/23" }); //데이터 가져오기 var list = await context.Payments.OfType<CreditCardPayment>().ToListAsync(); foreach (var creditCardPayment in list) { Console.WriteLine($"Id: {creditCardPayment.Id}, Amount: {creditCardPayment.Amount}, PaymentDate: {creditCardPayment.PaymentDate}, Status: {creditCardPayment.Status}, CardNumber: {creditCardPayment.CardNumber}, CardHolder: {creditCardPayment.CardHolder}, ExpirationDate: {creditCardPayment.ExpirationDate}"); }
테이블은 단한개로 유지되기 때문에 장입시의 사용하는 타입의 데이터 부분만 Insert 하는 것을 볼수있습니다.
가져올때는 OfType<T>() 를 통해서 가져올 수 있습니다.
이와 같이 TPH는 조회 성능을 극대화하는 대신, NULL 값이 많아질 수 있다는 점을 고려해야 하는 전략입니다.
EF Core의 HasDiscriminator()를 활용하면 손쉽게 구현할 수 있으며,
도메인 모델의 특성과 테이블 크기를 고려하여 적절한 전략을 선택하는 것이 중요합니다.'.NET > Database' 카테고리의 다른 글
50 EF Core Interview Questions (1~25) (0) 2025.03.11 EF Core 테이블 상속 매핑(TPC) (0) 2025.03.10 EF Core 테이블 상속 매핑 (TPT) (0) 2025.02.17 EF Core - (부록) Project Setup (0) 2025.02.03 EF Core - Code First (0) 2025.01.11