다음을 통해 공유


생성자가 있는 엔터티 형식

매개 변수를 사용하여 생성자를 정의하고 엔터티 인스턴스를 만들 때 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는 일반적인 방식으로 생성자를 호출한 후 설정합니다.
  • 매개 변수의 형식 및 이름은 속성의 형식 및 이름과 일치해야 합니다. 단, 속성은 Pascal 대/소문자를 사용할 수 있는 반면, 매개 변수는 camel 대/소문자를 사용해야 합니다.
  • 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);
        });
}

참고할 사항:

  • 이제 키 "property"가 필드입니다. 저장소 생성 키를 사용할 수 있도록 하는 readonly 필드가 아닙니다.
  • 다른 속성은 생성자에서만 설정된 읽기 전용 속성입니다.
  • 기본 키 값이 EF에서만 설정되거나 데이터베이스에서 읽는 경우 생성자에 포함할 필요가 없습니다. 이렇게 하면 키 "속성"이 단순 필드로 남고 새 블로그 또는 게시물을 만들 때 명시적으로 설정해서는 안 된다는 것을 분명히 합니다.

메모

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

서비스 삽입

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인 경우를 방어적으로 처리합니다.
  • 서비스는 읽기/쓰기 속성에 저장되므로 엔터티가 새 컨텍스트 인스턴스에 연결되면 다시 설정됩니다.

경고

이와 같이 DbContext를 삽입하는 것은 엔터티 형식을 EF Core에 직접 결합하기 때문에 종종 안티 패턴으로 간주됩니다. 다음과 같이 서비스 주입을 사용하기 전에 모든 옵션을 신중하게 고려합니다.