具有建構函式的實體類型
您可以使用參數定義建構函式,並在建立實體的實例時讓 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 會在以一般方式呼叫建構函式之後加以設定。
- 參數類型和名稱必須符合屬性類型和名稱,不同之處在于屬性在參數大小寫時可以是 Pascal 大小寫。
- EF Core 無法使用建構函式來設定導覽屬性(例如上述部落格或文章)。
- 建構函式可以是公用、私人或具有任何其他協助工具。 不過,延遲載入 Proxy 需要從繼承的 Proxy 類別存取建構函式。 這通常表示將其設定為公用或受保護。
唯讀屬性
一旦透過建構函式設定屬性,讓其中一些屬性成為唯讀,就有意義。 EF Core 支援這項功能,但有一些需要注意的事項:
- 沒有 setter 的屬性不會依慣例對應。 (這樣做通常會對應不應對應的屬性,例如計算屬性。
- 使用自動產生的索引鍵值需要可讀寫的索引鍵屬性,因為金鑰值必須在插入新實體時由金鑰產生器設定。
避免這些事項的簡單方式是使用私人 setter。 例如:
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 會將具有私用 setter 的屬性視為讀寫,這表示所有屬性都與之前一樣對應,而且金鑰仍可儲存產生。
使用私人 setter 的替代方法是讓屬性真正唯讀,並在 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 呼叫,而且有另一個公用建構函式可供一般使用。
- 使用插入服務的程式碼(也就是內容)是防禦性的,就是
null
處理 EF Core 未建立實例的情況。 - 因為服務會儲存在讀取/寫入屬性中,所以當實體附加至新的內容實例時,將會重設服務。
警告
插入這類 DbCoNtext 通常被視為反模式,因為它會將實體類型直接結合至 EF Core。 請先仔細考慮所有選項,再像這樣使用服務插入。