陰影屬性是指那些在 .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");
});
}
}
財產袋實體類型可用於任何使用正常實體類型的地方,包括作為擁有實體類型。 然而,它們確實有一些限制:
- 它們不可能有陰影屬性。
- 索引器導覽不支援
- 繼承不被支援
- 部分關係模型建構 API 缺乏對共享型態實體類型的超載功能
- 其他類型則無法標示為財產袋