관계 탐색
EF Core 관계는 외래 키에 의해 정의됩니다. 탐색은 외부 키 위에 계층화되어 관계를 읽고 조작하기 위한 자연스러운 개체 지향 보기를 제공합니다. 애플리케이션은 탐색을 사용하여 외래 키 값에 어떤 일이 발생하는지 신경 쓰지 않고 엔터티 그래프로 작업할 수 있습니다.
Important
여러 관계가 탐색을 공유할 수 없습니다. 모든 외래 키는 보안 주체에서 종속으로의 최대 하나의 탐색 및 종속 키에서 보안 주체로의 최대 하나의 탐색에 연결할 수 있습니다.
참조 탐색
탐색은 참조 및 컬렉션이라는 두 가지 형식으로 제공됩니다. 참조 탐색은 다른 엔터티에 대한 간단한 개체 참조입니다. 일대다 및 일대일 관계의 "일" 측면을 나타냅니다. 예시:
public Blog TheBlog { get; set; }
참조 탐색은 공용일 필요는 없지만 setter가 있어야 합니다. 참조 탐색은 null이 아닌 기본값으로 자동으로 초기화해서는 안 됩니다. 이렇게 하는 것은 엔터티가 존재하지 않을 때 존재하는 것을 어설션하는 것과 같습니다.
C# null 허용 참조 형식을 사용하는 경우 선택적 관계에 대해 참조 탐색을 null 허용해야 합니다.
public Blog? TheBlog { get; set; }
필요한 관계에 대한 참조 탐색은 null을 허용하거나 null을 허용하지 않을 수 있습니다.
컬렉션 탐색
컬렉션 탐색은 .NET 컬렉션 형식의 인스턴스입니다. 즉, ICollection<T>을 구현하는 모든 형식입니다. 컬렉션에는 관련 엔터티 형식의 인스턴스가 포함되며, 그 중 숫자가 있을 수 있습니다. 일대다 및 다대다 관계의 "다" 측면을 나타냅니다. 예시:
public ICollection<Post> ThePosts { get; set; }
컬렉션 탐색에는 setter가 필요하지 않습니다. 컬렉션을 인라인으로 초기화하는 것이 일반적이므로 속성이 null
인지 확인할 필요가 없습니다. 예시:
public ICollection<Post> ThePosts { get; } = new List<Post>();
팁
public ICollection<Post> ThePosts => new List<Post>();
과 같은 식 본문 속성을 실수로 만들지 마세요. 이렇게 하면 속성에 액세스할 때마다 비어 있는 새 컬렉션 인스턴스가 만들어지므로 탐색으로는 쓸모가 없습니다.
컬렉션 형식
기본 컬렉션 인스턴스는 ICollection<T>을 구현해야 하며 작동하는 Add
메서드가 있어야 합니다. List<T> 또는 HashSet<T>를 사용하는 것이 일반적입니다. List<T>
는 적은 수의 관련 엔터티에 효율적이며 안정적인 순서를 유지 관리합니다. HashSet<T>
는 많은 엔터티에 대해 보다 효율적인 조회를 수행하지만 안정적인 순서가 없습니다. 사용자 고유의 사용자 지정 컬렉션 구현을 사용할 수도 있습니다.
Important
컬렉션은 참조 같음을 사용해야 합니다. 컬렉션 탐색에 대한 HashSet<T>
를 만들 때는 ReferenceEqualityComparer를 사용해야 합니다.
배열은 ICollection<T>
을 구현하더라도 Add
메서드가 호출될 때 예외를 throw하므로 컬렉션 탐색에 사용할 수 없습니다.
컬렉션 인스턴스는 ICollection<T>
이어야 하지만 컬렉션은 이와 같이 노출될 필요가 없습니다. 예를 들어 애플리케이션 코드에서 임의로 수정할 수 없는 읽기 전용 보기를 제공하는 탐색을 IEnumerable<T>로 노출하는 것이 일반적입니다. 예시:
public class Blog
{
public int Id { get; set; }
public IEnumerable<Post> ThePosts { get; } = new List<Post>();
}
이 패턴의 변형에는 필요에 따라 컬렉션을 조작하는 메서드가 포함됩니다. 예시:
public class Blog
{
private readonly List<Post> _posts = new();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts;
public void AddPost(Post post) => _posts.Add(post);
}
애플리케이션 코드는 여전히 노출된 컬렉션을 ICollection<T>
으로 강제 변환한 다음 조작할 수 있습니다. 이 문제가 있는 경우 엔터티는 컬렉션의 방어 복사본을 반환할 수 있습니다. 예시:
public class Blog
{
private readonly List<Post> _posts = new();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts.ToList();
public void AddPost(Post post) => _posts.Add(post);
}
탐색에 액세스할 때마다 컬렉션의 복사본을 만드는 오버헤드보다 충분히 높은 값인지 신중하게 고려합니다.
팁
이 최종 패턴은 기본적으로 EF가 해당 지원 필드를 통해 컬렉션에 액세스하기 때문에 작동합니다. 즉, EF 자체는 실제 컬렉션에서 엔터티를 추가하고 제거하는 반면 애플리케이션은 컬렉션의 방어 복사본과만 상호 작용합니다.
컬렉션 탐색 초기화
컬렉션 탐색은 엔터티 형식에 의해 즉시 초기화할 수 있습니다.
public class Blog
{
public ICollection<Post> Posts { get; } = new List<Post>();
}
또는 지연:
public class Blog
{
private ICollection<Post>? _posts;
public ICollection<Post> Posts => _posts ??= new List<Post>();
}
예를 들어 쿼리를 실행하는 동안 EF에서 컬렉션 탐색에 엔터티를 추가해야 하는 경우 현재 null
인 경우 컬렉션을 초기화합니다. 만든 인스턴스는 탐색의 노출된 형식에 따라 달라집니다.
- 탐색이
HashSet<T>
로 노출되면 ReferenceEqualityComparer를 사용하는HashSet<T>
인스턴스가 만들어집니다. - 그렇지 않으면 탐색이 매개 변수가 없는 생성자를 사용하여 구체적인 형식으로 노출되면 해당 콘크리트 형식의 인스턴스가 만들어집니다.
List<T>
에 적용되지만 사용자 지정 컬렉션 형식을 비롯한 다른 컬렉션 형식에도 적용됩니다. - 그렇지 않은 경우 탐색이
IEnumerable<T>
,ICollection<T>
또는ISet<T>
로 노출되면ReferenceEqualityComparer
를 사용하는HashSet<T>
의 인스턴스가 만들어집니다. - 그렇지 않은 경우 탐색이
IList<T>
로 노출되면List<T>
의 인스턴스가 만들어집니다. - 그러지 않으면 예외가 throw됩니다.
참고 항목
변경 내용 추적 프록시를 포함하여 알림 엔터티가 사용되는 경우 List<T>
및 HashSet<T>
대신 ObservableCollection<T> 및 ObservableHashSet<T>가 사용됩니다.
Important
변경 내용 추적 설명서에 설명된 대로 EF는 지정된 키 값을 가진 엔터티의 단일 인스턴스만 추적합니다. 즉, 탐색으로 사용되는 컬렉션은 참조 같음 의미 체계를 사용해야 합니다. 개체 같음을 재정의하지 않는 엔터티 형식은 기본적으로 이를 가져옵니다. 탐색으로 사용할 HashSet<T>
를 만들 때 ReferenceEqualityComparer를 사용하여 모든 엔터티 형식에 대해 작동해야 합니다.
탐색 구성
탐색은 관계 구성의 일부로 모델에 포함됩니다. 즉, 규칙 또는 모델 빌드 API에서 HasOne
, HasMany
등을 사용하여 그렇게 됩니다. 탐색과 관련된 대부분의 구성은 관계 자체를 구성하여 수행됩니다.
그러나 전체 관계 구성의 일부가 아닌 탐색 속성 자체와 관련된 몇 가지 유형의 구성이 있습니다. 이 유형의 구성은 Navigation
메서드를 사용하여 수행됩니다. 예를 들어 EF가 지원 필드를 사용하는 대신 해당 속성을 통해 탐색에 액세스하도록 강제합니다.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Navigation(e => e.Posts)
.UsePropertyAccessMode(PropertyAccessMode.Property);
modelBuilder.Entity<Post>()
.Navigation(e => e.Blog)
.UsePropertyAccessMode(PropertyAccessMode.Property);
}
참고 항목
Navigation
호출은 탐색 속성을 만드는 데 사용할 수 없습니다. 관계를 정의하거나 규칙에서 이전에 만든 탐색 속성을 구성하는 데만 사용됩니다.
필수 탐색
관계가 필요한 경우 종속에서 보안 주체로의 탐색이 필요하며, 이는 외래 키 속성이 null을 허용하지 않는다는 것을 의미합니다. 반대로 외래 키가 null을 허용하므로 관계가 선택적이면 탐색이 선택 사항입니다.
보안 주체에서 종속 항목으로의 참조 탐색은 다릅니다. 대부분의 경우 보안 주체 엔터티는 종속 엔터티 없이 항상 존재할 수 있습니다. 즉, 필수 관계는 항상 최소 하나의 종속 엔터티가 있음을 나타내지 않습니다. EF 모델에는 방법이 없으며 관계형 데이터베이스에 표준 방법도 없으므로 보안 주체가 특정 개수의 종속 항목과 연결되어 있는지 확인할 수 있습니다. 필요한 경우 애플리케이션(비즈니스) 논리에서 구현해야 합니다.
이 규칙에는 한 가지 예외가 있습니다. 보안 주체 및 종속 형식이 관계형 데이터베이스에서 동일한 테이블을 공유하거나 문서에 포함된 경우입니다. 이 문제는 소유 형식 또는 동일한 테이블을 공유하는 소유되지 않은 형식에서 발생할 수 있습니다. 이 경우 보안 주체에서 종속 항목으로의 탐색 속성을 필수로 표시하여 종속 항목이 있어야 함을 나타낼 수 있습니다.
필요에 따라 탐색 속성의 구성은 Navigation
메서드를 사용하여 수행됩니다. 예시:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Navigation(e => e.BlogHeader)
.IsRequired();
}
.NET