생성자가 있는 엔터티 형식

매개 변수를 사용하여 생성자를 정의하고 엔터티 인스턴스를 만들 때 EF Core에서 이 생성자를 호출하도록 할 수 있습니다. 생성자 매개 변수는 매핑된 속성 또는 다양한 종류의 서비스에 바인딩되어 지연 로드와 같은 동작을 용이하게 할 수 있습니다.

참고 항목

현재 모든 생성자 바인딩은 규칙을 따릅니다. 사용할 특정 생성자의 구성은 향후 릴리스될 계획입니다.

매핑된 속성에 바인딩

일반적인 블로그/게시물 모델을 고려합니다.

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

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

EF Core는 쿼리 결과와 같이 이러한 형식의 인스턴스를 만들 때 먼저 매개 변수 없는 기본 생성자를 호출한 다음 각 속성을 데이터베이스의 값으로 설정합니다. 그러나 EF Core가 매핑된 속성과 일치하는 매개 변수 이름 및 형식이 있는 매개 변수화된 생성자를 찾으면 해당 속성 값으로 매개 변수화된 생성자를 호출하고 각 속성을 명시적으로 설정하지 않습니다. 예시:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

유의 사항:

  • 모든 속성에 생성자 매개 변수가 있어야 하는 것은 아닙니다. 예를 들어 Post.Content 속성은 생성자 매개 변수로 설정되지 않으므로 EF Core는 일반적인 방식으로 생성자를 호출한 후 설정합니다.
  • 매개 변수 형식 및 이름은 속성 형식 및 이름과 일치해야 합니다. 단, 매개 변수는 카멜 표기법을 사용하는 반면 속성은 파스칼 표기법을 사용할 수 있습니다.
  • EF Core는 생성자를 사용하여 탐색 속성(예: 위의 블로그 또는 게시물)을 설정할 수 없습니다.
  • 생성자는 공개 또는 비공개일 수 있으며, 다른 접근성이 설정될 수도 있습니다. 그러나 지연 로드 프록시를 사용하려면 상속 프록시 클래스에서 생성자에 액세스할 수 있어야 합니다. 일반적으로 이는 공개 또는 보호됨을 의미합니다.

읽기 전용 속성

속성이 생성자를 통해 설정되면 일부 속성을 읽기 전용으로 만드는 것이 좋을 수 있습니다. EF Core는 이를 지원하지만 다음과 같은 몇 가지 사항을 확인해야 합니다.

  • setter가 없는 속성은 규칙에 따라 매핑되지 않습니다. 이렇게 하면 계산된 속성과 같이 매핑되지 않아야 하는 속성을 매핑하는 경향이 있습니다.
  • 자동으로 생성된 키 값을 사용하려면 새 엔터티를 삽입할 때 키 생성기에서 키 값을 설정해야 하므로 읽기-쓰기가 가능한 키 속성이 필요합니다.

비공개 setter를 사용하면 이러한 상황을 간단하게 피할 수 있습니다. 예시:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }
    public string Author { get; private set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; private set; }

    public string Title { get; private set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; private set; }

    public Blog Blog { get; set; }
}

EF Core는 비공개 setter가 있는 속성을 읽기-쓰기로 간주합니다. 즉, 모든 속성은 이전과 같이 매핑되고 키는 여전히 저장소에서 생성될 수 있습니다.

비공개 setter를 사용하는 대신 속성을 실제로 읽기 전용으로 만들고 OnModelCreating에서 더욱 명시적인 매핑을 추가하는 것입니다. 마찬가지로 일부 속성을 완전히 제거하고 필드로만 바꿀 수 있습니다. 예를 들어 다음과 같은 엔터티 형식을 고려합니다.

public class Blog
{
    private int _id;

    public Blog(string name, string author)
    {
        Name = name;
        Author = author;
    }

    public string Name { get; }
    public string Author { get; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    private int _id;

    public Post(string title, DateTime postedOn)
    {
        Title = title;
        PostedOn = postedOn;
    }

    public string Title { get; }
    public string Content { get; set; }
    public DateTime PostedOn { get; }

    public Blog Blog { get; set; }
}

OnModelCreating의 이 구성은 다음과 같습니다.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Author);
            b.Property(e => e.Name);
        });

    modelBuilder.Entity<Post>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Title);
            b.Property(e => e.PostedOn);
        });
}

참고할 사항:

  • 키 ‘속성’은 이제 필드입니다. 저장소에서 생성된 키를 사용할 수 있는 readonly 필드가 아닙니다.
  • 다른 속성은 생성자에서만 설정된 읽기 전용 속성입니다.
  • 기본 키 값이 EF에서만 설정되거나 데이터베이스에서 읽는 경우 생성자에 포함할 필요가 없습니다. 이렇게 하면 키 ‘속성’은 단순 필드로 남겨지며, 새 블로그 또는 게시물을 작성할 때 명시적으로 설정해서는 안 된다는 점이 명확해 집니다.

참고 항목

이 코드를 사용하면 해당 필드가 사용되지 않음을 나타내는 컴파일러 경고 '169'가 발생합니다. 실제로 EF Core는 언어 외적인 방식으로 필드를 사용하므로 이를 무시할 수 있습니다.

서비스 삽입

EF Core는 엔터티 형식의 생성자에 ‘서비스’를 삽입할 수도 있습니다. 예를 들어 다음을 삽입할 수 있습니다.

  • DbContext - 파생된 DbContext 형식으로 입력할 수도 있는 현재 컨텍스트 인스턴스
  • ILazyLoader - 지연 로드 서비스: 자세한 내용은 지연 로드 설명서 참조
  • Action<object, string> - 지연 로드 대리자: 자세한 내용은 지연 로드 설명서 참조
  • IEntityType - 이 엔터티 형식과 연결된 EF Core 메타데이터

참고 항목

현재 EF Core에서 알려진 서비스만 삽입할 수 있습니다. 애플리케이션 서비스 삽입에 대한 지원은 향후 릴리스에서 고려될 예정입니다.

예를 들어 삽입된 DbContext를 통해 데이터베이스에 선택적으로 액세스하여 모든 엔터티를 로드하지 않고도 관련 엔터티에 대한 정보를 가져올 수 있습니다. 아래 예제에서는 게시물을 로드하지 않고 블로그의 게시물 수를 가져오는 데 사용합니다.

public class Blog
{
    public Blog()
    {
    }

    private Blog(BloggingContext context)
    {
        Context = context;
    }

    private BloggingContext Context { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; set; }

    public int PostsCount
        => Posts?.Count
           ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
           ?? 0;
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

이에 대해 몇 가지 참고 사항이 있습니다.

  • 생성자는 EF Core에서만 호출되므로 비공개이며, 일반적으로 사용되는 다른 공개 생성자가 있습니다.
  • 삽입된 서비스(즉, 컨텍스트)를 사용하는 코드는 EF Core가 인스턴스를 만들지 않는 경우를 처리하기 위해 null이 되지 않도록 방지합니다.
  • 서비스는 읽기/쓰기 속성에 저장되므로 엔터티가 새 컨텍스트 인스턴스에 연결되면 다시 설정됩니다.

Warning

이와 같이 DbContext를 삽입하는 것은 엔터티 형식을 EF Core에 직접 결합하므로 안티 패턴으로 간주되는 경우가 많습니다. 이와 같이 서비스 삽입을 사용하기 전에 모든 옵션을 신중하게 고려하세요.