Share via


Shadow 和 Indexer 屬性

陰影屬性是您 .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> 支援作為屬性包實體類型。 它必須設定為具有唯一名稱的共用類型實體類型,而且必須使用呼叫實Set作對應的DbSet屬性。

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

無論使用一般實體類型,都可以使用屬性包實體類型,包括作為擁有的實體類型。 不過,它們確實有某些限制: