天天看點

ABP架構 - 實體

文檔目錄

本節内容:

  • 實體類
  • 聚合根類
    • 領域事件
  • 約定的接口
    • 審計
    • 軟删除
    • 活躍/消極 實體
  • 實體變化事件
  • IEntity 接口

實體是DDD一個核心的概念。Eric Evans是這麼描述的:“一個對象根本上不是按它的特性定義的,而是按一個線程的連續性和身份來定義”。是以實體有一個id屬性存入資料庫中。一個實體通常映射成關系型資料庫的一個表。

在ABP裡,實體從Entity類上繼承,示例代碼如下:

public class Person : Entity
{
    public virtual string Name { get; set; }

    public virtual DateTime CreationTime { get; set; }

    public Person()
    {
        CreationTime = DateTime.Now;
    }
}      

Person類定義成一個實體,它有兩個屬性,同時Entity類定義了一個Id屬性,它是這個實體的主鍵。是以所有的實體主鍵名都相同,都是Id。

Id(主鍵)的類型是可改的,預設是int(Int32)。如果你想把Id定義成其它類型,你應該顯式聲明它,如下所示:

public class Person : Entity<long>
{
    public virtual string Name { get; set; }

    public virtual DateTime CreationTime { get; set; }

    public Person()
    {
        CreationTime = DateTime.Now;
    }
}      

同樣,你也可以把它設定成string,Guid或其它類型。

Entity類重寫了equality操作符(==),用它可以非常容易地檢查兩個實體是否相等(它們的Id是否相等),同時也定義了IsTransient()方法檢查實體是否有一個Id。

“聚合在DDD裡是一個模式,一個DDD聚合是一個領域對象群,可由單獨的單元建立。例如一個訂單和它的項,這些可以是分離的對象,但把訂單(和它的項一起)看成是一個單獨的聚合是有好處的。“(Martin Fowler 檢視完整描述)。

雖然ABP沒有強迫你使用聚合,但你也可能想在你的應用裡,建立聚合和聚合根。ABP擴充了Entity,定義了AggregateRoot類,為一個聚合建立聚合根實體。

AggregateRoot定義了DomainEvents集合,通過聚合根對象産生領域事件。這些事件在目前工作單元完成前自動觸發,實質上,任何實體都可以通過實作IGeneratesDomainEvents接口産生領域事件,但通常(最佳實踐)在聚合根裡産生領域事件,這就是為什麼把它預設到AggregateRoot裡,而不是Entity類裡。

在很多應用裡,有很多相似的實體屬性(資料庫表的字段),如表示實體何時建立的CreationTime,ABP提供了一些有用的接口,明确和展現這些通用屬性,這也給實作這些接口的實體,在編寫這些屬性代碼時提供了一種通用的方式。

IHasCreationTime為一個實體的“建立時間”資訊采用通用的屬性,在一個實體插入到資料庫前,ABP自動為實作了該接口的實體,設定CreationTime屬性為目前時間。

public interface IHasCreationTime
{
    DateTime CreationTime { get; set; }
}      

Person類改寫成實作IHasCreationTime接口,如下所示:

public class Person : Entity<long>, IHasCreationTime
{
    public virtual string Name { get; set; }

    public virtual DateTime CreationTime { get; set; }

    public Person()
    {
        CreationTime = DateTime.Now;
    }
}      

ICreationAudited通過添加CreatorUserId擴充了IhasCreationTime:

public interface ICreationAudited : IHasCreationTime
{
    long? CreatorUserId { get; set; }
}      

當儲存一個新實體時,ABP自動把CreatorUserId設定為目前使用者的id。你也可以讓你的類繼承CreationAuditedEntity類實作ICreationAudited。它同時也有一個适用于不同類型Id屬性的泛型版本。

也有一個類似的“修改”接口

public interface IHasModificationTime
{
    DateTime? LastModificationTime { get; set; }
}

public interface IModificationAudited : IHasModificationTime
{
    long? LastModifierUserId { get; set; }
}      

當更新一個實體時,ABP也自動設定這些屬性。你隻需要為你的類定義它們就可以。

如果你想實作所有審計屬性,你可以直接實作IAudited接口:

public interface IAudited : ICreationAudited, IModificationAudited
{

}      

更快捷的方式是:你可以繼承AuditedEntity類來代替直接實作IAudited。AuditiedEntity類同樣也有一個适用于不同類型Id屬性的泛型版本。

注意:ABP從ABP會話裡擷取使用者Id。

軟删除是一個通用的模式,它把一個實體标記為“已删除”代替從資料庫直接删除。例如,你不想把一個User從資料庫硬删除,因為它可能與其它表有關聯,ISoftDelete接口就是出于這種目的:

public interface ISoftDelete
{
    bool IsDeleted { get; set; }
}      

ABP實作了開箱即用模式的軟删除模式。當一個軟删除實體開始删除時,ABP檢測它,阻止它被删除,設定IsDeleted為true,并把實體更新到資料庫。同時,ABP不會從資料庫擷取(select)軟删除的實體,會自動過濾它們。

如果你使用軟删除,當軟删除一個實體時,你可能也會想儲存是誰删除和什麼時候删除,你可以實作IDeletionAudited接口,如下所示:

public interface IDeletionAudited : ISoftDelete
{
    long? DeleterUserId { get; set; }

    DateTime? DeletionTime { get; set; }
}      

更快捷的方式是:你可以從已經實作了所有的FullAuditedEntity類繼承你的實體。

  • 注意1:所有審計接口和類都有一個為指向你的User實體的導航屬性而設計的泛型版本(如ICreationAudited<TUser>和FullAuditedEntity<TPrimaryKey,TUser>)。
  • 注意2:同時,它們都有一個AggregateRoot版本,如AuditedAggregateRoot。

有些實體需要标記為Active(活躍的)和Passive(消極的),然後你根據實體的活躍/消極狀态進行不同的操作,IPassivable就是為此而設計的,它定義了IsActive屬性。

如果你的實體想在建立時就是處于活躍狀态,你可以在構造器裡設定IsActive為true。

它與軟删除(IsDeleted)不同,如果一個實體被軟删除,Abp預設不從資料庫裡取出,但是是否擷取活躍/消極實體完全取決于你。

當一個實體插入、更新、删除時,ABP會自動觸發某些事件,是以你可以注冊這些事件執行你需要的任何邏輯。檢視事件總線文檔的“預定義事件”主題,擷取更多資訊。

實質上,Entity類實作了IEntity接口(且Entity<TPrimaryKey>實作了IEntity<TPrimaryKey>)。如果你不想從Entity類繼承,你可以直接實作這些接口,這些接口對于其它實體類也是适用的,但是這不是推薦的方式,除非你有一個好的理由。

kid1412聲明:轉載請把此段聲明完整地置于文章頁面明顯處,并保留個人在部落格園的連結:http://www.cnblogs.com/kid1412/(可點選跳轉)。