ABP踩坑記錄-目錄html
這裏我以問答模塊爲例,記錄一下我在建立實體類過程當中碰到的一些坑。git
具體什麼是審計屬性我這裏就再也不介紹了,你們能夠參考官方文檔。github
這裏我是經過繼承定義好的基類來得到相應的審計屬性,你們若是有需求的話,也能夠本身經過接口定義。app
其中,abp提供的審計基類有兩種,一種只包含UserId的FullAuditedEntity<TPrimaryKey>
,另外一種則是添加了User的導航屬性的FullAuditedEntity<TPrimaryKey, TUser>
,後一種可方便以後用AutoMapper來獲取用戶信息。less
FullAuditedEntity
實質爲FullAuditedEntity<int>
ide
這裏可能會出現的坑就是一時手誤會寫成FullAuditedEntity<User>
,這樣的話它是把User類型實體的主鍵,算是不容易察覺的坑。函數
根據約定,在定義好實體間導航關係以後,EF Core會爲其自動建立關係。ui
但在實際開發中,有時咱們並不但願將一些導航屬性暴露出來,例如:Image
類理應包含指向Question
和Answer
的導航屬性。爲此,咱們能夠經過隱藏屬性(Shadow Properties)來化解這一尷尬。this
在QincaiDbContext
中,咱們重載OnModelCreating
方法:code
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Image>(e => { // 添加隱藏屬性 e.Property<int>("QuestionId"); // 配置外鍵 e.HasOne(typeof(Question)) .WithMany(nameof(Question.Images)) .HasForeignKey("QuestionId"); }); }
以上就是完整的步驟,固然有人會以爲奇怪由於徹底不作配置也是能夠用的,這是EF Core已經根據約定自動爲咱們建立了隱藏屬性:
Shadow properties can be created by convention when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced. The shadow foreign key property will be named
<navigation property name><principal key property name>
(the navigation on the dependent entity, which points to the principal entity, is used for the naming).-- From Microsoft Docs
這裏EF Core爲咱們建立的隱藏屬性將命名爲<導航屬性名稱><對應主鍵名稱>
,即像咱們這裏有一個導航屬性Question
,其Question
類的主鍵爲Id
,那麼隱藏屬性就是QuestionId
。
在一些特殊狀況下,咱們所需的主鍵多是由多個屬性決定的,好比QuestionTag
就是以QuestionId
和TagName
爲主鍵。
這裏咱們須要經過Fluent API來進行配置,在重載的OnModelCreating
方法中添加:
modelBuilder.Entity<QuestionTag>(qt => { qt.HasKey(e => new { e.QuestionId, e.TagName }); });
經過表達式的形式,咱們能夠很方便的建立新的複合主鍵。
另外,由於在QuestionTag
中的真正主鍵是QuestionId
和TagName
,因此咱們還須要覆蓋掉繼承來的Id
屬性:
public class QuestionTag : Entity<string> { /// <summary> /// 無效Id,實際Id爲QuestionId和TagName /// </summary> [NotMapped] public override string Id => $"{QuestionId}-{TagName}"; /// <summary> /// 問題Id /// </summary> public int QuestionId { get; set; } /// <summary> /// 標籤名稱 /// </summary> public string TagName { get; set; } // ... }
在官方文檔中,使用默認值的方式是在構造函數中賦值,這裏我使用的是C# 6.0中的屬性初始化語法(Auto-property initializers)。從我目前的結果來講,與預期效果基本一致,並且更易於閱讀。
形式以下:
public class Question : FullAuditedAggregateRoot<int, User>, IPassivable { /// <summary> /// 問題狀態(默認爲true) /// </summary> public bool IsActive { get; set; } = true; // ... }
這是個一直被我忽略的地方,在此以前經常使用的是默認空構造函數,但若須要一個有參構造函數,且這個參數並不直接對應某個屬性,如:
// 此處僅爲舉例說明 public class Question { public Category Category { get; set; } // ... // 這裏構造的參數並不直接對應某個屬性 public Question(string categoryName) { Category = new Category { Name = categoryName }; } }
當你添加遷移的時候就會報以下錯誤:No suitable constructor found for entity type 'Question'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'categoryName' in 'Question(string categoryName)'.
大概就是EF Core不能推斷出categoryName
是什麼。
解決方法很簡單,手動添加一個空構造函數便可。
按照常識,咱們添加新的構造函數:
public class Question { // ... // 空的構造函數 public Question() {} }
可事實上,咱們並不但願有人使用這個空的構造函數,由於它會缺乏一些空值檢測等斷定。
通過查找資料,我在微軟的eShopOnWeb示例項目中找到了以下寫法:
public class Order : BaseEntity, IAggregateRoot { // 注意這裏是private private Order() { // required by EF } // 含參構造函數包括了空值檢測 public Order(string buyerId, Address shipToAddress, List<OrderItem> items) { Guard.Against.NullOrEmpty(buyerId, nameof(buyerId)); Guard.Against.Null(shipToAddress, nameof(shipToAddress)); Guard.Against.Null(items, nameof(items)); BuyerId = buyerId; ShipToAddress = shipToAddress; _orderItems = items; } public string BuyerId { get; private set; } public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now; public Address ShipToAddress { get; private set; } private readonly List<OrderItem> _orderItems = new List<OrderItem>(); public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); // ... }
回過頭,我又去確認了EF Core的文檔:
When EF Core creates instances of these types, such as for the results of a query, it will first call the default parameterless constructor and then set each property to the value from the database
...
The constructor can be public, private, or have any other accessibility.
-- From Microsoft Docs
也就是,EF Core在建立實例時,會首先去調用無參構造函數,且不管該構造函數是何訪問類型。
那麼問題就解決了,咱們只需添加私有的無參構造函數便可。
PS:但仍是沒找到EF Core是如何調用私有構造的過程,但願知道的大佬能指點一下。