共用方式為


影子與索引器的特性

陰影屬性是指那些在 .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 現有索引器屬性的名稱相符,程式碼就會設定該現有屬性。 如果實體類型有一個屬性,而該屬性是由實體類別上的屬性所支持,則會拋出例外,因為索引器的屬性必須只能透過索引器存取。

索引器屬性可以透過上述靜態方法或使用 CLR 索引器特性,在 LINQ 查詢 EF.Property 中被引用。

財產袋實體類型

僅包含索引器屬性的實體類型稱為屬性袋實體類型。 這些實體類型沒有影子屬性,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");
            });
    }
}

財產袋實體類型可用於任何使用正常實體類型的地方,包括作為擁有實體類型。 然而,它們確實有一些限制: