文檔目錄
本節內容:
實體是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類繼承你的實體。
活躍/消極 實體
有些實體需要標記為Active(活躍的)和Passive(消極的),然後你根據實體的活躍/消極狀態采取行動。你可以實現為此目的而生的IPassivable,它定義了IsActive屬性。
如果你的實體想在創建時就是處理活躍狀態,你可以在構造器裡設置IsActive為true。
這與軟刪除(IsDeleted)不同,如果一個實體被軟刪除,它就不能再從數據庫裡獲取到(ABP默認阻止它),但是是否獲取活躍/消極實體完全取決於你。
實體變化事件
當一個實體插入、更新、刪除時,ABP會自動觸發某些事件,因此你可以注冊這些事件執行你需要的任何邏輯。查看事件總線文檔的“預定義事件”主題,獲取更多信息。
IEntity 接口
實質上,Entity類實現了IEntity接口(且Entity<TPrimaryKey>實現了IEntity<TPrimaryKey>)。如果你不想從Entity類繼承,你可以直接實現這些接口,這些接口對於其它實體類也是適用的,但是這不是推薦的方式,除非你有一個好的理由。