シャドウとインデクサーのプロパティ

シャドウ プロパティは、.NET エンティティ クラスでは定義されていないが、EF Core モデル内でそのエンティティ型に対して定義されるプロパティです。 これらのプロパティの値と状態は、変更トラッカーでそのまま保持されます。 シャドウ プロパティは、マップされたエンティティ型で公開すべきではないデータがデータベースに存在する場合に便利です。

インデクサー プロパティはエンティティ型のプロパティで、.NET エンティティ クラスのインデクサーによってサポートされます。 .NET クラス インスタンスのインデクサーを使用してアクセスできます。 また、CLR クラスを変更せずにエンティティ型にプロパティを追加できます。

外部キーのシャドウ プロパティ

シャドウ プロパティは、外部キー プロパティに最もよく使用されます。これは、規則によって外部キー プロパティが見つからないか、明示的に構成されている場合に、規則によってモデルに追加されます。 リレーションシップはナビゲーション プロパティによって表されますが、データベースでは外部キー制約によって適用され、外部キー列の値は対応するシャドウ プロパティに格納されます。

プロパティには <navigation property name><principal key property name> という名前が付けられます (プリンシパル エンティティを参照する依存エンティティのナビゲーションが名前付けに使用されます)。 プリンシパル キーのプロパティ名がナビゲーション プロパティの名前で始まる場合、名前は単に <principal key property name> となります。 依存エンティティにナビゲーション プロパティがない場合は、主キー プロパティ名または代替キー プロパティ名と連結されたプリンシパル型名が、<principal type name><principal key property name> の代わりに使われます。

たとえば、次のコード リストでは、BlogId シャドウ プロパティが Post エンティティに導入されます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

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

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    // Since there is no CLR property which holds the foreign
    // key for this relationship, a shadow property is created.
    public Blog Blog { get; set; }
}

シャドウ プロパティの構成

Fluent API を使用して、シャドウ プロパティを構成できます。 Property<TProperty>(String) の文字列オーバーロードを呼び出したら、他のプロパティに対して行う構成呼び出をチェーンできます。 次の例では、Blog には LastUpdated という名前の CLR プロパティがないので、シャドウ プロパティが作成されます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property<DateTime>("LastUpdated");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Property メソッドに指定された名前が既存のプロパティ (シャドウ プロパティまたはエンティティ クラスで定義されているもの) の名前と一致する場合、コードは、新しいシャドウ プロパティを導入するのではなく、その既存のプロパティを構成します。

シャドウ プロパティへのアクセス

シャドウ プロパティの値は、ChangeTracker API を使用して取得および変更できます。

context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;

シャドウ プロパティは、EF.Property 静的メソッドを使用して LINQ クエリで参照できます。

var blogs = context.Blogs
    .OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));

返されたエンティティは変更トラッカーによって追跡されないため、追跡なしクエリの後にシャドウ プロパティにアクセスすることはできません。

インデクサー プロパティの構成

Fluent API を使用して、インデクサー プロパティを構成できます。 メソッド IndexerProperty を呼び出したら、他のプロパティに対して行う構成呼び出をチェーンできます。 次の例では、Blog にインデクサーが定義されており、インデクサー プロパティの作成に使用されます。

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().IndexerProperty<DateTime>("LastUpdated");
    }
}

public class Blog
{
    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
    public int BlogId { get; set; }

    public object this[string key]
    {
        get => _data[key];
        set => _data[key] = value;
    }
}

IndexerProperty メソッドに指定された名前が既存のインデクサー プロパティの名前と一致する場合、コードはその既存のプロパティが構成されます。 エンティティ型にエンティティ クラスのプロパティによってサポートされる プロパティがある場合、例外がスローされます。これは、インデクサー プロパティにはインデクサー経由でのみアクセスする必要があるためです。

インデクサー のプロパティは、上記のように EF.Property 静的メソッドを使用するか、CLR インデクサー プロパティを使用して LINQ クエリで参照できます。

プロパティ バッグ エンティティ型

インデクサー プロパティのみを含むエンティティ型は、プロパティ バッグ エンティティ型と呼ばれます。 これらのエンティティ型にはシャドウ プロパティがないため、EF では代わりにインデクサー プロパティが作成されます。 現在は、Dictionary<string, object> のみがプロパティ バッグ エンティティ型としてサポートされています。 これは一意の名前を持つ共有型エンティティ型として構成する必要があります。また、対応する DbSet プロパティは Set 呼び出しを使用して実装する必要があります。

internal class MyContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>(
            "Blog", bb =>
            {
                bb.Property<int>("BlogId");
                bb.Property<string>("Url");
                bb.Property<DateTime>("LastUpdated");
            });
    }
}

プロパティ バッグ エンティティ型は、通常のエンティティ型が使用されるすべての場所で使用できます (所有エンティティ型としての使用を含む)。 ただし、次の制限があります。