ABP框架入門踩坑-添加實體

添加實體

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類理應包含指向QuestionAnswer的導航屬性。爲此,咱們能夠經過隱藏屬性(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就是以QuestionIdTagName爲主鍵。

這裏咱們須要經過Fluent API來進行配置,在重載的OnModelCreating方法中添加:

modelBuilder.Entity<QuestionTag>(qt =>
{
    qt.HasKey(e => new { e.QuestionId, e.TagName });
});

經過表達式的形式,咱們能夠很方便的建立新的複合主鍵。

另外,由於在QuestionTag中的真正主鍵是QuestionIdTagName,因此咱們還須要覆蓋掉繼承來的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是如何調用私有構造的過程,但願知道的大佬能指點一下。

相關文章
相關標籤/搜索