コンストラクターを使用したエンティティ型Entity types with constructors

注意

これは EF Core 2.1 の新機能です。This feature is new in EF Core 2.1.

EF Core 2.1 以降では、パラメーターを使用 EF Core してコンストラクターを定義し、エンティティのインスタンスを作成するときにこのコンストラクターを呼び出すことができるようになりました。Starting with EF Core 2.1, it is now possible to define a constructor with parameters and have EF Core call this constructor when creating an instance of the entity. コンストラクターのパラメーターは、マップされたプロパティ、またはさまざまな種類のサービスにバインドして、遅延読み込みなどの動作を容易にすることができます。The constructor parameters can be bound to mapped properties, or to various kinds of services to facilitate behaviors like lazy-loading.

注意

EF Core 2.1 の場合、すべてのコンストラクターのバインドは規約に従っています。As of EF Core 2.1, all constructor binding is by convention. 使用する特定のコンストラクターの構成は、将来のリリースで計画されています。Configuration of specific constructors to use is planned for a future release.

マップされたプロパティへのバインドBinding to mapped properties

一般的なブログ/ポストモデルを考えてみましょう。Consider a typical Blog/Post model:

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 は、クエリの結果など、これらの型のインスタンスを作成するときに、最初に既定のパラメーターなしのコンストラクターを呼び出し、次に各プロパティをデータベースの値に設定します。When EF Core creates instances of these types, such as for the results of a query, it will first call the default parameterless constructor and then set each property to the value from the database. ただし、パラメーター名と型がマップされたプロパティと一致するパラメーター化されたコンストラクターが EF Core によって検出された場合は、代わりに、これらのプロパティの値を使用してパラメーター化されたコンストラクターが呼び出され、各プロパティが明示的に設定されることはありません。However, if EF Core finds a parameterized constructor with parameter names and types that match those of mapped properties, then it will instead call the parameterized constructor with values for those properties and will not set each property explicitly. 次に例を示します。For example:

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; }
}

注意事項:Some things to note:

  • すべてのプロパティにコンストラクターパラメーターを設定する必要があるわけではありません。Not all properties need to have constructor parameters. たとえば、Post. Content プロパティは、コンストラクターのパラメーターによって設定されないため、EF Core はコンストラクターを通常の方法で呼び出した後に設定します。For example, the Post.Content property is not set by any constructor parameter, so EF Core will set it after calling the constructor in the normal way.
  • パラメーターの型と名前は、プロパティの型と名前を一致させる必要があります。ただし、パラメーターは、Pascal 形式ですが、パラメーターは camel 形式にすることができます。The parameter types and names must match property types and names, except that properties can be Pascal-cased while the parameters are camel-cased.
  • EF Core は、コンストラクターを使用して、ナビゲーションプロパティ (ブログや投稿など) を設定することはできません。EF Core cannot set navigation properties (such as Blog or Posts above) using a constructor.
  • コンストラクターは、パブリック、プライベート、またはその他のアクセシビリティを持つことができます。The constructor can be public, private, or have any other accessibility. ただし、レイジー読み込みプロキシでは、継承プロキシクラスからコンストラクターにアクセスできる必要があります。However, lazy-loading proxies require that the constructor is accessible from the inheriting proxy class. 通常、これはパブリックまたは保護されていることを意味します。Usually this means making it either public or protected.

読み取り専用プロパティRead-only properties

コンストラクターを使用してプロパティを設定したら、それらの一部を読み取り専用にすることが理にかなっています。Once properties are being set via the constructor it can make sense to make some of them read-only. EF Core はこれをサポートしていますが、次の点を確認してください。EF Core supports this, but there are some things to look out for:

  • Setter のないプロパティは規約によってマップされません。Properties without setters are not mapped by convention. (これにより、計算されたプロパティなど、マップしないプロパティがマップされる傾向があります)。(Doing so tends to map properties that should not be mapped, such as computed properties.)
  • 自動的に生成されたキー値を使用するには、新しいエンティティを挿入するときにキージェネレーターによってキー値が設定される必要があるため、読み取り/書き込み可能なキープロパティが必要です。Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities.

これらの作業を回避する簡単な方法は、プライベート setter を使用することです。An easy way to avoid these things is to use private setters. 次に例を示します。For example:

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 を持つプロパティを読み取り/書き込み可能として認識します。つまり、すべてのプロパティが以前としてマップされ、キーを格納して生成することができます。EF Core sees a property with a private setter as read-write, which means that all properties are mapped as before and the key can still be store-generated.

プライベート setter を使用する代わりに、プロパティを実際に読み取り専用にし、OnModelCreating で明示的なマッピングを追加することもできます。An alternative to using private setters is to make properties really read-only and add more explicit mapping in OnModelCreating. 同様に、一部のプロパティを完全に削除し、フィールドのみに置き換えることができます。Likewise, some properties can be removed completely and replaced with only fields. たとえば、次のようなエンティティ型を考えてみます。For example, consider these entity types:

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 のこの構成は次のように作成されます。And this configuration in 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);
        });
}

注意する点:Things to note:

  • キー "property" はフィールドになりました。The key "property" is now a field. これは、ストアによって生成され readonly たキーを使用できるようにするためのフィールドではありません。It is not a readonly field so that store-generated keys can be used.
  • その他のプロパティは、コンストラクターでのみ設定される読み取り専用プロパティです。The other properties are read-only properties set only in the constructor.
  • 主キーの値が EF によってのみ設定された場合、またはデータベースから読み取られた場合は、それをコンストラクターに含める必要はありません。If the primary key value is only ever set by EF or read from the database, then there is no need to include it in the constructor. これにより、キー "プロパティ" は単純なフィールドとして残され、新しいブログや投稿を作成するときに明示的に設定する必要がないことがわかります。This leaves the key "property" as a simple field and makes it clear that it should not be set explicitly when creating new blogs or posts.

注意

このコードでは、フィールドが使用されていないことを示すコンパイラ警告 ' 169 ' が生成されます。This code will result in compiler warning '169' indicating that the field is never used. 実際には、extralinguistic の方法でフィールドが使用されているため、これは無視でき EF Core。This can be ignored since in reality EF Core is using the field in an extralinguistic manner.

サービスの挿入Injecting services

EF Core は、エンティティ型のコンストラクターに "services" を挿入することもできます。EF Core can also inject "services" into an entity type's constructor. たとえば、次のコードを挿入できます。For example, the following can be injected:

  • DbContext -現在のコンテキストインスタンス。派生した DbContext 型として型指定することもできます。DbContext - the current context instance, which can also be typed as your derived DbContext type
  • ILazyLoader -レイジー読み込みサービス-詳細については、 遅延読み込みのドキュメント を参照してください。ILazyLoader - the lazy-loading service--see the lazy-loading documentation for more details
  • Action<object, string> -遅延読み込みデリゲート-詳細については、 遅延読み込みのドキュメント を参照してください。Action<object, string> - a lazy-loading delegate--see the lazy-loading documentation for more details
  • IEntityType -このエンティティ型に関連付けられている EF Core メタデータIEntityType - the EF Core metadata associated with this entity type

注意

EF Core 2.1 の場合、EF Core によって認識されるサービスのみを挿入できます。As of EF Core 2.1, only services known by EF Core can be injected. アプリケーションサービスの挿入のサポートは、今後のリリースで考慮されています。Support for injecting application services is being considered for a future release.

たとえば、挿入された DbContext を使用して、データベースに選択的にアクセスし、それらのエンティティをすべて読み込まずに関連エンティティに関する情報を取得することができます。For example, an injected DbContext can be used to selectively access the database to obtain information about related entities without loading them all. 次の例では、投稿を読み込まずにブログ内の投稿数を取得するために使用されています。In the example below this is used to obtain the number of posts in a blog without loading the posts:

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; }
}

これについては、次の点に注意してください。A few things to notice about this:

  • コンストラクターは EF Core によって呼び出されるだけなのでプライベートです。一般に使用するための別のパブリックコンストラクターもあります。The constructor is private, since it is only ever called by EF Core, and there is another public constructor for general use.
  • 挿入されたサービスを使用するコード (つまり、コンテキスト) は、 null EF Core がインスタンスを作成していないケースを処理するために防御されます。The code using the injected service (that is, the context) is defensive against it being null to handle cases where EF Core is not creating the instance.
  • サービスは読み取り/書き込みプロパティに格納されるため、エンティティが新しいコンテキストインスタンスにアタッチされるときにリセットされます。Because service is stored in a read/write property it will be reset when the entity is attached to a new context instance.

警告

このような DbContext の挿入は、エンティティ型を EF Core に直接結合するため、多くの場合、アンチパターンと見なされます。Injecting the DbContext like this is often considered an anti-pattern since it couples your entity types directly to EF Core. このようなサービスインジェクションを使用する前に、すべてのオプションを慎重に検討してください。Carefully consider all options before using service injection like this.