次の方法で共有


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

エンティティのインスタンスを作成するときに、パラメーターを持つコンストラクターを定義し、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);
        });
}

考慮事項:

  • キー「property」がフィールドになりました。 ストアで生成されたキーを使用できるように、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 に対して防御的です。
  • サービスは読み取り/書き込みプロパティに格納されるため、エンティティが新しいコンテキスト インスタンスにアタッチされるとリセットされます。

Warnung

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