コンストラクターを含むエンティティ型

パラメーターを使用してコンストラクターを定義し、エンティティのインスタンスを作成するときに、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 では、コンストラクターを使用してナビゲーション プロパティ (上記のブログや投稿など) を設定することはできません。
  • コンストラクターは、パブリック、プライベート、または他のアクセシビリティを持つ場合があります。 ただし、遅延読み込みプロキシでは、継承するプロキシ クラスからコンストラクターにアクセスできる必要があります。 つまり、これは public または protected のいずれかになります。

読み取り専用プロパティ

コンストラクターを使用してプロパティを設定したら、その一部を読み取り専用にすることもできます。 これは EF Core でサポートされていますが、次のことに注意が必要です。

  • 慣例により、セッターのないプロパティはマップされません。 (これを行う場合、計算済みプロパティなど、マップすべきではないプロパティをマップする傾向があります。)
  • 自動的に生成されたキー値を使用するには、読み取り/書き込みであるキー プロパティが必要です。これは、新しいエンティティを挿入するときにキー ジェネレーターによってキー値を設定する必要があるためです。

これらの問題を回避する簡単な方法は、プライベート セッターを使用する方法です。 次に例を示します。

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 では、プライベート セッターを持つプロパティが読み取り/書き込みとして表示されます。つまり、すべてのプロパティは以前と同様にマップされ、キーは引き続きストアで生成できます。

プライベート セッターを使用する代わりに、プロパティを実際に読み取り専用にし、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 - 遅延読み込みサービス - 詳細については、ILazyLoaderを参照してください
  • Action<object, string> - 遅延読み込みデリゲート - 詳細については、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 になることに対して防御的です。
  • サービスは読み取り/書き込みプロパティに格納されるため、エンティティが新しいコンテキスト インスタンスにアタッチされたときにリセットされます。

警告

このような DbContext の挿入は、エンティティ型を EF Core に直接結合するため、しばしばアンチパターンとみなされます。 このようなサービス インジェクションを使用する前に、すべての選択肢を慎重に検討してください。