관계의 외래 키 및 보안 주체 키
모든 일대일 및 일대다 관계는 주 엔드의 기본 또는 대체 키를 참조하는 종속 끝의 외래 키에 의해 정의됩니다. 편의를 위해 이 기본 또는 대체 키를 관계의 "주 키"라고 합니다. 다대다 관계는 각각 주 키를 참조하는 외래 키에 의해 정의되는 두 개의 일대다 관계로 구성됩니다.
팁
아래 코드는 ForeignAndPrincipalKeys.cs에서 찾을 수 있습니다.
외래 키
외래 키를 구성하는 속성 또는 속성은 규칙에 의해 발견되는 경우가 많습니다. 매핑 특성 또는 모델 빌드 API의 HasForeignKey
를 사용하여 속성을 명시적으로 구성할 수도 있습니다. HasForeignKey
는 람다 식과 함께 사용할 수 있습니다. 예를 들어 단일 속성으로 구성된 외래 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.ContainingBlogId);
}
또는 두 개 이상의 속성으로 구성된 복합 외래 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}
팁
모델 빌드 API에서 람다 식을 사용하면 코드 분석 및 리팩터링에 속성 사용을 사용할 수 있으며 추가 연결 메서드에서 사용할 수 있도록 API에 속성 형식을 제공합니다.
HasForeignKey
는 외래 키 속성의 이름을 문자열로 전달할 수도 있습니다. 예를 들어 단일 속성의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("ContainingBlogId");
}
또는 복합 외래 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}
문자열 사용은 다음과 같은 경우에 유용합니다.
- 속성은 private입니다.
- 속성 또는 속성은 엔터티 형식에 존재하지 않으며 섀도 속성을 만들어야 합니다.
- 속성 이름은 모델 빌드 프로세스에 대한 일부 입력에 따라 계산되거나 생성됩니다.
null을 허용하지 않는 외래 키 열
선택적 관계 및 필수 관계에 설명된 대로 외래 키 속성의 null 허용 여부가 관계 선택 사항인지 필수인지를 결정합니다. 그러나 nullable 외래 키 속성은 [Required]
특성을 사용하거나 모델 빌드 API에서 IsRequired
를 호출하여 필요한 관계에 사용할 수 있습니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
또는 외래 키가 규칙에 의해 발견되는 경우 HasForeignKey
를 호출하지 않고 IsRequired
를 사용할 수 있습니다.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.IsRequired();
}
결과적으로 외래 키 속성이 null을 허용하더라도 데이터베이스의 외래 키 열은 null을 허용하지 않습니다. 필요에 따라 외래 키 속성 자체를 명시적으로 구성하여 동일한 작업을 수행할 수 있습니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.Property(e => e.BlogId)
.IsRequired();
}
섀도 외세 키
외래 키 속성은 섀도 속성으로 만들 수 있습니다. 섀도 속성은 EF 모델에 있지만 .NET 형식에는 존재하지 않습니다. EF는 내부적으로 속성 값 및 상태를 추적합니다.
섀도 외래 키는 일반적으로 애플리케이션 코드/비즈니스 논리에서 사용하는 도메인 모델에서 외래 키의 관계형 개념을 숨기려는 경우에 사용됩니다. 그런 다음, 이 애플리케이션 코드는 탐색을 통해 관계를 완전히 조작합니다.
팁
예를 들어, 엔터티가 직렬화될 경우(예: 와이어를 통해 전송) 외래 키 값은 엔터티가 개체/그래프 형식에 없을 때 관계 정보를 그대로 유지하는 유용한 방법이 될 수 있습니다. 따라서 이러한 목적을 위해 .NET 형식에 외래 키 속성을 유지하는 것이 실용적입니다. 외래 키 속성은 프라이빗이 될 수 있으며, 이는 해당 값이 엔터티와 함께 이동하도록 허용하면서 외래 키가 노출되지 않도록 하는 좋은 타협입니다.
섀도 외래 키 속성은 규칙에 의해 만들어지는 경우가 많습니다. HasForeignKey
에 대한 인수가 .NET 속성과 일치하지 않는 경우에도 섀도 외래 키가 만들어집니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("MyBlogId");
}
규칙에 따라 섀도 외래 키는 관계의 주 키에서 해당 형식을 가져옵니다. 이 형식은 관계가 필요에 따라 검색되거나 구성되지 않는 한 null 허용으로 설정됩니다.
섀도 외래 키 속성을 명시적으로 만들 수도 있습니다. 이 속성은 속성의 패싯을 구성하는 데 유용합니다. 예를 들어 null을 허용하지 않는 속성을 만들려면 다음을 수행합니다.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.Property<string>("MyBlogId")
.IsRequired();
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("MyBlogId");
}
팁
규칙에 따라 외래 키 속성은 관계의 주 키에서 최대 길이 및 유니코드 지원과 같은 패싯을 상속합니다. 따라서 외래 키 속성에서 패싯을 명시적으로 구성할 필요가 거의 없습니다.
지정된 이름이 엔터티 형식의 속성과 일치하지 않는 경우 섀도 속성 만들기는 ConfigureWarnings
를 사용하여 사용하지 않도록 설정할 수 있습니다. 예시:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));
외래 키 제약 조건 이름
규칙에 따라 외래 키 제약 조건에 FK_<dependent type name>_<principal type name>_<foreign key property name>
이라는 이름이 지정됩니다. 복합 인덱스의 경우 <foreign key property name>
은 밑줄로 구분된 속성 이름 목록이 됩니다.
HasConstraintName
을 사용하여 API를 빌드하는 모델에서 변경할 수 있습니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.HasConstraintName("My_BlogId_Constraint");
}
팁
제약 조건 이름은 EF 런타임에서 사용되지 않습니다. EF Core 마이그레이션을 사용하여 데이터베이스 스키마를 만들 때만 사용됩니다.
외래 키에 대한 인덱스
규칙에 따라 EF는 외래 키의 속성 또는 속성에 대한 데이터베이스 인덱스가 만들어집니다. 규칙에 의해 생성된 인덱스 유형에 대한 자세한 내용은 모델 빌드 규칙을 참조하세요.
팁
관계는 해당 모델에 포함된 엔터티 형식 간의 EF 모델에 정의됩니다. 일부 관계는 다른 컨텍스트의 모델에서 엔터티 형식을 참조해야 할 수 있습니다(예: BoundedContext 패턴을 사용하는 경우). 이러한 경우 외래 키 열을 일반 속성에 매핑해야 하며 이러한 속성을 수동으로 조작하여 관계 변경을 처리할 수 있습니다.
보안 주체 키
규칙에 따라 외래 키는 관계의 주 끝 부분에 있는 기본 키로 제한됩니다. 그러나 대체 키를 대신 사용할 수 있습니다. 이는 모델 빌드 API에서 HasPrincipalKey
를 사용하여 수행됩니다. 예를 들어 단일 속성 외래 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId);
}
또는 여러 속성이 있는 복합 외래 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}
HasPrincipalKey
는 대체 키 속성의 이름을 문자열로 전달할 수도 있습니다. 예를 들어 단일 속성 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey("AlternateId");
}
또는 복합 키의 경우:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey("AlternateId1", "AlternateId2");
}
참고 항목
보안 주체 및 외래 키의 속성 순서가 일치해야 합니다. 또한 키가 데이터베이스 스키마에 정의된 순서이기도 합니다. 엔터티 형식의 속성 또는 테이블의 열 순서와 같을 필요는 없습니다.
주 엔터티에서 대체 키를 정의하기 위해 HasAlternateKey
를 호출할 필요가 없습니다. 이 작업은 기본 키 속성이 아닌 속성과 함께 HasPrincipalKey
가 사용될 때 자동으로 수행됩니다. 그러나 HasAlternateKey
는 데이터베이스 제약 조건 이름을 설정하는 등 대체 키를 추가로 구성하는 데 사용할 수 있습니다. 자세한 내용은 키를 참조하세요.
키 없는 엔터티에 대한 관계
모든 관계에는 보안 주체(기본 또는 대체) 키를 참조하는 외래 키가 있어야 합니다. 즉, 키 없는 엔터티 형식은 참조할 외래 키에 대한 보안 주체 키가 없으므로 관계의 주된 끝으로 작동할 수 없습니다.
팁
엔터티 형식에는 대체 키가 있을 수 없지만 기본 키는 있을 수 없습니다. 이 경우 대체 키(또는 여러 개 있는 경우 대체 키 중 하나)를 기본 키로 승격해야 합니다.
그러나 키 없는 엔터티 형식은 여전히 외래 키를 정의할 수 있으므로 관계의 종속 끝으로 작동할 수 있습니다. 예를 들어 Tag
에 키가 없는 다음 형식을 고려해 보세요.
public class Tag
{
public string Text { get; set; } = null!;
public int PostId { get; set; }
public Post Post { get; set; } = null!;
}
public class Post
{
public int Id { get; set; }
}
Tag
는 관계의 종속 끝에서 구성할 수 있습니다.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasNoKey();
modelBuilder.Entity<Post>()
.HasMany<Tag>()
.WithOne(e => e.Post);
}
참고 항목
EF는 키가 없는 엔터티 형식을 가리키는 탐색을 지원하지 않습니다. GitHub 이슈 #30331을 참조하세요.
다대다 관계의 외래 키
다대다 관계에서 외래 키는 조인 엔터티 형식에 정의되고 조인 테이블의 외래 키 제약 조건에 매핑됩니다. 위에서 설명한 모든 항목은 이러한 조인 엔터티 외신 키에도 적용할 수 있습니다. 예를 들어 데이터베이스 제약 조건 이름을 설정합니다.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(e => e.Tags)
.WithMany(e => e.Posts)
.UsingEntity(
l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}
.NET