다음을 통해 공유


관계 검색에 대한 규칙

EF Core는 엔터티 형식 클래스를 기반으로 모델을 검색하고 빌드할 때 규칙 집합을 사용합니다. 이 문서에서는 엔터티 형식 간의 관계를 검색하고 구성하는 데 사용되는 규칙을 요약합니다.

Important

여기에 설명된 규칙은 매핑 특성 또는 모델 빌드 API를 사용하여 관계의 명시적 구성을 통해 재정의할 수 있습니다.

아래 코드는 RelationshipConventions.cs에서 찾을 수 있습니다.

탐색 검색

관계 검색은 엔터티 형식 간의 탐색을 검색하여 시작합니다.

참조 탐색

엔터티 형식의 속성은 다음과 같은 경우 참조 탐색으로 검색됩니다.

  • 속성이 공용입니다.
  • 속성에 getter와 setter가 있습니다.
    • setter는 공용일 필요가 없습니다. 프라이빗이거나 다른 접근성이 있을 수 있습니다.
    • setter는 Init 전용일 수 있습니다.
  • 속성 형식은 엔터티 형식이거나 그럴 수 있습니다. 이는 형식이 다음과 같다는 것을 의미합니다.
    • 참조 형식이어야 합니다.
    • 기본 속성 형식으로 명시적으로 구성되지 않아야 합니다.
    • 사용 중인 데이터베이스 공급자가 기본 속성 형식으로 매핑해서는 안 됩니다.
    • 사용 중인 데이터베이스 공급자가 매핑한 기본 속성 형식으로 자동으로 변환 가능하지 않아야 합니다.
  • 속성이 정적이지 않습니다.
  • 속성이 인덱서 속성이 아닙니다.

예를 들어 다음 엔터티 형식을 살펴봅니다.

public class Blog
{
    // Not discovered as reference navigations:
    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public Uri? Uri { get; set; }
    public ConsoleKeyInfo ConsoleKeyInfo { get; set; }
    public Author DefaultAuthor => new() { Name = $"Author of the blog {Title}" };

    // Discovered as a reference navigation:
    public Author? Author { get; private set; }
}

public class Author
{
    // Not discovered as reference navigations:
    public Guid Id { get; set; }
    public string Name { get; set; } = null!;
    public int BlogId { get; set; }

    // Discovered as a reference navigation:
    public Blog Blog { get; init; } = null!;
}

이러한 형식의 경우 Blog.AuthorAuthor.Blog는 참조 탐색으로 검색됩니다. 반면 다음 속성은 참조 탐색으로 검색되지 않습니다.

  • Blog.Id(int가 매핑된 기본 형식이므로)
  • Blog.Title('string'이 매핑된 기본 형식이므로)
  • Blog.Uri(Uri가 매핑된 기본 형식으로 자동으로 변환되므로)
  • Blog.ConsoleKeyInfo(ConsoleKeyInfo가 C# 값 형식이므로)
  • Blog.DefaultAuthor(속성에 setter가 없으므로)
  • Author.Id(Guid가 매핑된 기본 형식이므로)
  • Author.Name('string'이 매핑된 기본 형식이므로)
  • Author.BlogId(int가 매핑된 기본 형식이므로)

컬렉션 탐색

엔터티 형식의 속성은 다음과 같은 경우 컬렉션 탐색으로 검색됩니다.

  • 속성이 공용입니다.
  • 속성에 getter가 있습니다. 컬렉션 탐색에는 setter가 있을 수 있지만 필수는 아닙니다.
  • 속성 형식은 IEnumerable<TEntity>이거나 이를 구현하며 여기서 TEntity는 엔터티 형식이거나 그럴 수 있습니다. 이는 TEntity 형식이 다음과 같다는 것을 의미합니다.
    • 참조 형식이어야 합니다.
    • 기본 속성 형식으로 명시적으로 구성되지 않아야 합니다.
    • 사용 중인 데이터베이스 공급자가 기본 속성 형식으로 매핑해서는 안 됩니다.
    • 사용 중인 데이터베이스 공급자가 매핑한 기본 속성 형식으로 자동으로 변환 가능하지 않아야 합니다.
  • 속성이 정적이지 않습니다.
  • 속성이 인덱서 속성이 아닙니다.

예를 들어 다음 코드에서 Blog.TagsTag.Blogs 모두 컬렉션 탐색으로 검색됩니다.

public class Blog
{
    public int Id { get; set; }
    public List<Tag> Tags { get; set; } = null!;
}

public class Tag
{
    public Guid Id { get; set; }
    public IEnumerable<Blog> Blogs { get; } = new List<Blog>();
}

탐색 페어링

예를 들어 엔터티 형식 A에서 엔터티 형식 B로의 탐색이 검색되면 이 탐색에 반대 방향으로 역방향이 있는지, 즉 엔터티 형식 B에서 엔터티 형식 A로 가는지 확인해야 합니다. 이러한 역이 발견되면 두 탐색이 쌍을 이루어 단일 양방향 관계를 형성합니다.

관계의 형식은 탐색과 해당 역방향이 참조 탐색인지 컬렉션 탐색인지에 따라 결정됩니다. 특별한 사항

  • 한 탐색이 컬렉션 탐색이고 다른 탐색이 참조 탐색인 경우 관계는 일대다입니다.
  • 두 탐색이 모두 참조 탐색인 경우 관계는 일대일입니다.
  • 두 탐색이 모두 컬렉션 탐색인 경우 관계는 다대다입니다.

이러한 각 유형의 관계를 검색하는 방법은 아래 예제에 나와 있습니다.

Blog 간에 하나의 일대다 관계가 검색되며 Blog.PostsPost.Blog 탐색을 페어링하여 Post가 검색됩니다.

public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

Blog 간에 하나의 일대일 관계가 검색되며 Blog.AuthorAuthor.Blog 탐색을 페어링하여 Author가 검색됩니다.

public class Blog
{
    public int Id { get; set; }
    public Author? Author { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

Post 간에 하나의 다대다 관계가 검색되며 Post.TagsTag.Posts 탐색을 페어링하여 Tag가 검색됩니다.

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

참고 항목

두 탐색이 서로 다른 단방향 관계를 나타내는 경우 이 탐색 쌍이 올바르지 않을 수 있습니다. 이 경우 두 관계를 명시적으로 구성해야 합니다.

관계 쌍은 두 형식 간에 단일 관계가 있는 경우에만 작동합니다. 두 형식 간의 여러 관계를 명시적으로 구성해야 합니다.

참고 항목

여기에 설명된 내용은 서로 다른 두 형식 간의 관계입니다. 그러나 동일한 형식이 관계의 양쪽 끝에 있을 수 있으므로 단일 형식에 대해 두 탐색을 서로 쌍으로 연결할 수 있습니다. 이를 자체 참조 관계라고 합니다.

외래 키 속성 검색

관계에 대한 탐색이 명시적으로 검색되거나 구성되면 이러한 탐색을 사용하여 관계에 대한 적절한 외래 키 속성을 검색합니다. 속성은 다음과 같은 경우 외래 키로 검색됩니다.

  • 속성 형식은 주 엔터티 형식의 기본 또는 대체 키와 호환됩니다.
    • 형식이 같거나 외래 키 속성 형식이 기본 또는 대체 키 속성 형식의 null 허용 버전인 경우 호환됩니다.
  • 속성 이름은 외래 키 속성에 대한 명명 규칙 중 하나와 일치합니다. 명명 규칙은 다음과 같습니다.
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • 또한 종속 엔드가 모델 빌드 API를 사용하여 명시적으로 구성되고 종속 기본 키가 호환되는 경우 종속 기본 키도 외래 키로 사용됩니다.

"ID" 접미사는 대/소문자를 사용할 수 있습니다.

다음 엔터티 형식은 이러한 각 명명 규칙에 대한 예제를 보여 줍니다.

Post.TheBlogKey는 패턴 <navigation property name><principal key property name>과 일치하므로 외래 키로 검색됩니다.

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.TheBlogID는 패턴 <navigation property name>Id과 일치하므로 외래 키로 검색됩니다.

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogID { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.BlogKey는 패턴 <principal entity type name><principal key property name>과 일치하므로 외래 키로 검색됩니다.

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.Blogid는 패턴 <principal entity type name>Id과 일치하므로 외래 키로 검색됩니다.

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? Blogid { get; set; }
    public Blog? TheBlog { get; set; }
}

참고 항목

일대다 탐색의 경우 외래 키 속성은 종속 엔터티이므로 참조 탐색이 있는 형식에 있어야 합니다. 일대일 관계의 경우 외래 키 속성의 검색을 사용하여 관계의 종속 끝을 나타내는 형식을 결정합니다. 외래 키 속성이 검색되지 않은 경우 HasForeignKey를 사용하여 종속 엔드를 구성해야 합니다. 이에 대한 예제는 일대일 관계를 참조하세요.

위의 규칙은 복합 외래 키에도 적용됩니다. 여기서 복합의 각 속성은 기본 또는 대체 키의 해당 속성과 호환되는 형식을 가져야 하며 각 속성 이름은 위에서 설명한 명명 규칙 중 하나와 일치해야 합니다.

카디널리티 결정

EF는 검색된 탐색 및 외래 키 속성을 사용하여 보안 주체 및 종속 끝과 함께 관계의 카디널리티를 결정합니다.

  • 페어링되지 않은 참조 탐색이 있는 경우 관계는 단방향 일대다로 구성되며 종속 끝의 참조 탐색이 있습니다.
  • 페어링되지 않은 컬렉션 탐색이 있는 경우 관계는 단방향 일대다로 구성되며 주 끝의 컬렉션 탐색이 포함됩니다.
  • 쌍을 이루는 참조 및 컬렉션 탐색이 있는 경우 관계는 주 끝의 컬렉션 탐색과 함께 양방향 일대다로 구성됩니다.
  • 참조 탐색이 다른 참조 탐색과 쌍을 이루는 경우 다음을 수행합니다.
    • 외래 키 속성이 한쪽에서 검색되었지만 다른 쪽에서는 검색되지 않은 경우 관계는 종속 끝에 외래 키 속성을 사용하여 양방향 일대일로 구성됩니다.
    • 그렇지 않으면 종속 쪽을 확인할 수 없으며 EF는 종속을 명시적으로 구성해야 함을 나타내는 예외를 throw합니다.
  • 컬렉션 탐색이 다른 컬렉션 탐색과 쌍을 이루는 경우 관계는 양방향 다대다로 구성됩니다.

섀도 외래 키 속성

EF가 관계의 종속 끝을 결정했지만 외래 키 속성이 검색되지 않은 경우 EF는 외래 키를 나타내는 섀도 속성을 만듭니다. 섀도 속성:

  • 관계의 보안 주체 끝에 기본 또는 대체 키 속성의 형식이 있습니다.
    • 형식은 기본적으로 null 허용으로 설정되므로 기본적으로 관계는 선택 사항입니다.
  • 종속 끝에 탐색이 있는 경우 기본 또는 대체 키 속성 이름과 연결된 이 탐색 이름을 사용하여 섀도 외래 키 속성의 이름을 지정합니다.
  • 종속된 끝에 탐색이 없는 경우 기본 또는 대체 키 속성 이름과 연결된 주 엔터티 형식 이름을 사용하여 섀도 외래 키 속성의 이름을 지정합니다.

하위 삭제

규칙에 따라 필수 관계는 계단식 삭제로 구성됩니다. 선택적 관계는 계단식 삭제를 하지 않도록 구성됩니다.

다대다

다대다 관계에는 보안 주체 및 종속 끝이 없으며 두 끝 모두 외래 키 속성을 포함하지 않습니다. 대신 다대다 관계는 다대다의 양쪽 끝을 가리키는 외래 키 쌍을 포함하는 조인 엔터티 형식을 사용합니다. 규칙에 따라 다대다 관계가 검색되는 다음 엔터티 형식을 고려합니다.

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

이 검색에 사용되는 규칙은 다음과 같습니다.

  • 조인 엔터티 형식의 이름은 <left entity type name><right entity type name>입니다. 따라서 이 예제에서는 PostTag입니다.
    • 조인 테이블의 이름은 조인 엔터티 형식과 같습니다.
  • 조인 엔터티 형식에는 관계의 각 방향에 대한 외래 키 속성이 제공됩니다. 이름은 <navigation name><principal key name>입니다. 따라서 이 예제에서 외래 키 속성은 PostsIdTagsId입니다.
    • 단방향 다대다의 경우 연결된 탐색이 없는 외래 키 속성의 이름은 <principal entity type name><principal key name>입니다.
  • 외래 키 속성은 null을 허용하지 않으며 조인 엔터티에 대한 두 관계가 모두 필요합니다.
    • 연계 삭제 규칙은 이러한 관계가 연계 삭제를 위해 구성됨을 의미합니다.
  • 조인 엔터티 형식은 두 개의 외래 키 속성으로 구성된 복합 기본 키로 구성됩니다. 따라서 이 예제에서 기본 키는 PostsIdTagsId로 구성됩니다.

그러면 다음 EF 모델이 생성됩니다.

Model:
  EntityType: Post
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Tags (ICollection<Tag>) CollectionTag Inverse: Posts
    Keys:
      Id PK
  EntityType: Tag
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Posts (ICollection<Post>) CollectionPost Inverse: Tags
    Keys:
      Id PK
  EntityType: PostTag (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      PostsId (no field, int) Indexer Required PK FK AfterSave:Throw
      TagsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
    Keys:
      PostsId, TagsId PK
    Foreign keys:
      PostTag (Dictionary<string, object>) {'PostsId'} -> Post {'Id'} Cascade
      PostTag (Dictionary<string, object>) {'TagsId'} -> Tag {'Id'} Cascade
    Indexes:
      TagsId

그리고 SQLite를 사용하는 경우 다음 데이터베이스 스키마로 변환됩니다.

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tag" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tag_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tag" ("Id") ON DELETE CASCADE);

CREATE INDEX "IX_PostTag_TagsId" ON "PostTag" ("TagsId");

인덱스

규칙에 따라 EF는 외래 키의 속성에 대한 데이터베이스 인덱스를 만듭니다. 생성된 인덱스 형식은 다음을 통해 결정됩니다.

  • 카디널리티 관계의 끝.
  • 관계가 선택 사항인지 또는 필수인지 여부.
  • 외래 키를 구성하는 속성의 수

일대다 관계의 경우 규칙에 의해 간단한 인덱스가 만들어집니다. 선택적 관계와 필수 관계에 대해 동일한 인덱스가 만들어집니다. 예를 들어 SQLite에서:

CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");

또는 SQL Server에서:

CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);

필수 일대일 관계의 경우 고유한 인덱스가 만들어집니다. 예를 들어 SQLite에서:

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

또는 SQL Server에서:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);

선택적 일대일 관계의 경우 SQLite에서 만든 인덱스는 동일합니다.

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

그러나 SQL Server에서는 null 외래 키 값을 더 잘 처리하기 위해 IS NOT NULL 필터가 추가되었습니다. 예시:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;

복합 외래 키의 경우 모든 외래 키 열을 포함하는 인덱스가 생성됩니다. 예시:

CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");

참고 항목

EF는 기존 인덱스 또는 기본 키 제약 조건이 이미 적용되는 속성에 대한 인덱스를 만들지 않습니다.

외래 키에 대한 인덱스 생성을 EF를 중지하는 방법

인덱스에는 오버헤드가 있으며 여기에 설명된 대로 모든 FK 열에 대해 인덱스를 만드는 것이 항상 적절하지는 않을 수 있습니다. 이를 위해 ForeignKeyIndexConvention은 모델을 빌드할 때 제거할 수 있습니다.

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}

원하는 경우 인덱스가 필요한 외래 키 열에 대해 인덱스를 명시적으로 만들 수 있습니다.

외래 키 제약 조건 이름

규칙에 따라 외래 키 제약 조건에 FK_<dependent type name>_<principal type name>_<foreign key property name>이라는 이름이 지정됩니다. 복합 인덱스의 경우 <foreign key property name>은 밑줄로 구분된 속성 이름 목록이 됩니다.

추가 리소스