Entity Framework의 ID Keys에 대한 사랑: Composite Keys가 왜 번거로운가

발행: (2026년 1월 6일 오전 05:44 GMT+9)
12 min read
원문: Dev.to

Source: Dev.to

위의 링크에 포함된 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. 현재는 링크 자체만 제공되어 있어 번역할 본문이 없습니다. 텍스트를 복사해서 붙여 주시면 바로 번역해 드리겠습니다.

왜 Entity Framework는 기본 키를 요구하는가

Entity Framework는 근본적인 원칙에 기반합니다: 모든 엔터티는 고유하게 식별 가능해야 합니다. 이것은 임의의 규칙이 아니라 EF의 변경 추적, 관계 관리 및 데이터베이스 작업에 필수적입니다.

기본 키가 없으면 EF는 다음을 할 수 없습니다:

  • 수정된 엔터티를 추적할 수 없습니다
  • 엔터티 간 관계를 관리할 수 없습니다
  • 적절한 UPDATE 또는 DELETE 문을 생성할 수 없습니다
  • 아이덴티티‑맵 패턴을 유지할 수 없습니다 (주어진 키를 가진 엔터티 인스턴스가 메모리에 하나만 존재하도록 보장)

관례 기반 접근 방식

EF는 관례를 사용하여 개발자의 작업을 쉽게 합니다. 기본적으로 다음과 같은 이름의 속성을 찾습니다:

  • Id
  • Id
public class Product
{
    public int Id { get; set; }          // EF automatically recognizes this as the primary key
    public string Name { get; set; }
    public decimal Price { get; set; }
}

이러한 관례 기반 접근 방식은 대부분의 경우 구성 없이 사용할 수 있음을 의미합니다. 깔끔하고, 단순하며, 예측 가능합니다.

단일 키만으로는 부족할 때: 복합 키 도입

때때로 도메인 모델은 여러 컬럼으로 구성된 복합 키가 필요합니다. 전형적인 예는 다음과 같습니다:

  • 주문 라인 항목OrderIdLineNumber 두 개로 식별됩니다
  • 다대다 조인 테이블 – 양쪽의 외래 키를 사용합니다
  • 레거시 데이터베이스 – 이미 복합 키가 존재하는 경우
  • 자연 키CountryCode + StateCode와 같은 경우
public class OrderLineItem
{
    public int OrderId { get; set; }
    public int LineNumber { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}

겉보기엔 별다른 문제가 없어 보이죠? 하지만 여기서부터 상황이 복잡해집니다.

Source:

엔터티 프레임워크에서 복합 키의 복잡성

1. 관례 지원이 없음

단일 컬럼 키와 달리 EF는 관례만으로 복합 키를 자동으로 감지할 수 없습니다. 반드시 명시적으로 구성해야 합니다:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasKey(o => new { o.OrderId, o.LineNumber });
}

2. 탐색 속성 문제

관계에 복합 키가 포함될 경우, 구성 코드가 길어지고 오류가 발생하기 쉽습니다:

public class Order
{
    public int OrderId { get; set; }
    public List<LineItem> LineItems { get; set; }
}

public class OrderLineItem
{
    public int OrderId { get; set; }
    public int LineNumber { get; set; }
    public Order Order { get; set; }
}

// 필요한 구성:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasKey(o => new { o.OrderId, o.LineNumber });

    modelBuilder.Entity()
        .HasOne(o => o.Order)
        .WithMany(o => o.LineItems)
        .HasForeignKey(o => o.OrderId);
}

3. 엔터티 찾기가 번거로움

단일 키를 사용할 때는 엔터티를 찾는 것이 간단합니다:

var product = await context.Products.FindAsync(42);

복합 키를 사용할 경우 모든 키 값을 정확한 순서대로 제공해야 합니다:

var lineItem = await context.OrderLineItems.FindAsync(orderId, lineNumber);

순서를 잘못 전달하면 혼란스러운 결과나 예외가 발생합니다.

4. 식별 문제와 변경 추적

EF의 변경 추적기는 기본 키를 사용해 엔터티를 식별합니다. 복합 키가 중복되면 실패합니다:

var item1 = new OrderLineItem { OrderId = 1, LineNumber = 1, ProductName = "Widget" };
var item2 = new OrderLineItem { OrderId = 1, LineNumber = 1, ProductName = "Gadget" };

context.OrderLineItems.Add(item1);
context.OrderLineItems.Add(item2);

await context.SaveChangesAsync();   // 예외! 두 객체가 동일한 복합 키를 가짐

5. 다대다 관계가 복잡해짐

EF Core 5+에서는 자동 다대다 관계를 지원하지만, 이는 단순 조인 테이블에 가장 적합합니다. 복합 키나 추가 속성을 포함하면 명시적 구성이 필요합니다:

public class StudentCourse
{
    public int StudentId { get; set; }
    public int CourseId { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Grade { get; set; }

    public Student Student { get; set; }
    public Course Course { get; set; }
}

// 명시적 구성:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasKey(sc => new { sc.StudentId, sc.CourseId });

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

    modelBuilder.Entity()
        .HasOne(sc => sc.Course)
        .WithMany(c => c.StudentCourses)
        .HasForeignKey(sc => sc.CourseId);
}

6. 마이그레이션 및 데이터베이스 진화

초기 생성 후 복합 키를 변경하는 것은 고통스럽습니다:

  • 외래 키 제약을 삭제하고 다시 생성해야 함
  • 인덱스를 재구축해야 함
  • 데이터 마이그레이션 스크립트가 복잡해짐
  • 롤백 시나리오가 어려워짐

7. 성능 고려 사항

복합 키는 인덱스와 외래 키 컬럼의 크기를 증가시켜 다음과 같은 영향을 미칩니다:

  • 인덱스 페이지가 커짐 → I/O 증가
  • 특히 대용량 테이블에서 조인 연산이 느려짐
  • 변경 추적기에 대한 메모리 사용량 증가

요약

Entity Framework가 단일 기본 키를 강력히 선호하는 이유는 단순성, 성능, 그리고 신뢰할 수 있는 변경 추적 때문입니다. 복합 키도 지원되지만, 상당한 양의 보일러플레이트 설정을 요구하고 버그 위험을 높이며 마이그레이션 및 성능 튜닝을 복잡하게 만들 수 있습니다.

새 모델을 설계할 때, EF를 원활하게 사용하기 위해 대체 키(Id)를 도입할 수 있는지 고려하고, 복합 키는 정말 불가피한 레거시 상황에만 보류하십시오.

성능에 미치는 영향

  • 인덱스 크기 증가 (특히 넓은 복합 키의 경우)
  • 쿼리 계획이 더 복잡해짐
  • JOIN 연산이 느려짐

외래 키 인덱스는 데이터를 중복합니다

-- Single key index: 4 bytes (INT)
-- Composite key index with (INT, INT, GUID): 4 + 4 + 16 = 24 bytes
-- Multiply by millions of rows...

실제 사례 공포 이야기

다음은 복잡성이 얼마나 커질 수 있는지 보여주는 예시입니다:

public class TimeSheetEntry
{
    public int EmployeeId { get; set; }
    public DateTime Date { get; set; }
    public int ProjectId { get; set; }
    public int TaskId { get; set; }
    public decimal Hours { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Four‑column composite key!
    modelBuilder.Entity()
        .HasKey(t => new { t.EmployeeId, t.Date, t.ProjectId, t.TaskId });

    // Now imagine adding relationships to Employee, Project, and Task...
    // And trying to query this efficiently...
    // And explaining to junior developers why Find() needs four parameters...
}

Source:

더 나은 방법: 대리 키

대부분의 개발자와 Microsoft 자체 가이드에서는 자연 복합 키가 존재하더라도 대리 키(자동 증가 정수 또는 GUID)를 사용할 것을 권장합니다:

public class OrderLineItem
{
    public int Id { get; set; }               // Surrogate key
    public int OrderId { get; set; }
    public int LineNumber { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }

    public Order Order { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 고유 인덱스로 유일성을 강제합니다
    modelBuilder.Entity()
        .HasIndex(o => new { o.OrderId, o.LineNumber })
        .IsUnique();
}

장점

  • 모든 엔터티에 대해 간단하고 일관된 기본 키
  • 관계 설정이 용이
  • Find() 연산이 직관적
  • 대부분의 경우 더 나은 성능
  • 자연 키를 변경하더라도 관계가 깨지지 않는 유연성

복합 키가 의미가 있을 때

복잡함에도 불구하고 복합 키가 올바른 선택이 될 때:

  • 레거시 데이터베이스 통합 — 스키마를 변경할 수 없을 때
  • 순수 조인 테이블 — 추가 데이터가 없는 다대다 관계
  • 극도로 높은 성능이 요구되는 시나리오 — 대리키의 오버헤드가 중요한 경우
  • 도메인 모델링 — 복합 키가 실제로 엔터티의 정체성을 나타내는 경우

Conclusion

Entity Framework가 단일 컬럼 ID 키를 선호하는 것은 임의적인 것이 아니라 수십 년간의 ORM 경험과 실용적인 소프트웨어 엔지니어링에 기반합니다. 복합 키도 EF에서 작동하지만 코드, 설정, 그리고 사고 모델에 상당한 복잡성을 추가합니다.

복합 키를 사용하기 전에 스스로에게 물어보세요:

  • 이것이 정말 엔터티의 자연스러운 식별자인가, 아니면 단순히 고유 제약조건인가?
  • 6개월 후에도 그 복잡성이 가치가 있을까?
  • 대리 키와 고유 인덱스로 같은 목표를 달성할 수 있을까?

대부분의 경우, 답은 간단한 Id 속성을 유지하고 고유 인덱스를 사용해 자연키 제약을 강제하는 것입니다. 미래의 당신(그리고 팀원들)에게 감사할 것입니다.

Entity Framework에서 복합 키를 사용한 경험은 어떠신가요? 더 관리하기 쉽게 만드는 패턴을 찾으셨나요? 댓글로 여러분의 생각을 공유해주세요!

Back to Blog

관련 글

더 보기 »