領(lǐng)域?qū)?br />
實體是DDD(領(lǐng)域驅(qū)動設(shè)計)的核心概念之一。Eric Evans是這樣描述的“很多對象不是通過它們的屬性定義的,而是通過一連串的連續(xù)性事件和標識定義的”(引用領(lǐng)域驅(qū)動設(shè)計一書)。
譯者注:對象不是通過它們的屬性來下根本性的定義,而應(yīng)該是通過它的線性連續(xù)性和標識性定義的。。所以,實體是具有唯一標識的ID且存儲在數(shù)據(jù)庫中。實體通常被映射成數(shù)據(jù)庫中的一個表。
實體類(Entity classes)
在ABP中,實體繼承自Entity類,請看下面示例:
public class Person : Entity
{
public virtual string Name { get; set; }
public virtual DateTime CreationTime { get; set; }
public Task()
{
CreationTime = DateTime.Now;
}
}
Person 類被定義為一個實體。它具有兩個屬性,它的父類中有Id屬性。Id是該實體的主鍵。所以,Id是所有繼承自Entity類的實體的主鍵(所有實體的主鍵都是Id字段)。
Id(主鍵)數(shù)據(jù)類型可以被更改。默認是int(int32)類型。如果你想給Id定義其它類型,你應(yīng)該像下面示例一樣來聲明Id的類型。
public class Person : Entitylong>
{
public virtual string Name { get; set; }
public virtual DateTime CreationTime { get; set; }
public Task()
{
CreationTime = DateTime.Now;
}
}
你可以設(shè)置為string,Guid或者其它數(shù)據(jù)類型。
實體類重寫了 equality (==) 操作符用來判斷兩個實體對象是否相等(兩個實體的Id是否相等)。還定義了一個IsTransient()方法來檢測實體是否有Id屬性。
接口約定
在很多應(yīng)用程序中,很多實體具有像CreationTime的屬性(數(shù)據(jù)庫表也有該字段)用來指示該實體是什么時候被創(chuàng)建的。APB提供了一些有用的接口來實現(xiàn)這些類似的功能。也就是說,為這些實現(xiàn)了這些接口的實體,提供了一個通用的編碼方式(通俗的說只要實現(xiàn)指定的接口就能實現(xiàn)指定的功能)。
(1)審計(Auditing)
實體類實現(xiàn) IHasCreationTime 接口就可以具有CreationTime的屬性。當(dāng)該實體被插入到數(shù)據(jù)庫時, ABP會自動設(shè)置該屬性的值為當(dāng)前時間。
public interface IHasCreationTime
{
DateTime CreationTime { get; set; }
}
Person類可以被重寫像下面示例一樣實現(xiàn)IHasCreationTime 接口:
public class Person : Entitylong>, IHasCreationTime
{
public virtual string Name { get; set; }
public virtual DateTime CreationTime { get; set; }
public Task()
{
CreationTime = DateTime.Now;
}
}
ICreationAudited 擴展自 IHasCreationTime 并且該接口具有屬性 CreatorUserId :
public interface ICreationAudited : IHasCreationTime
{
long? CreatorUserId { get; set; }
}
當(dāng)保存一個新的實體時,ABP會自動設(shè)置CreatorUserId 的屬性值為當(dāng)前用戶的Id
你可以輕松的實現(xiàn)ICreationAudited接口,通過派生自實體類 CreationAuditedEntity (因為該類已經(jīng)實現(xiàn)了ICreationAudited接口,我們可以直接繼承CreationAuditedEntity 類就實現(xiàn)了上述功能)。它有一個實現(xiàn)不同ID數(shù)據(jù)類型的泛型版本(默認是int),可以為ID(Entity類中的ID)賦予不同的數(shù)據(jù)類型。
下面是一個為實現(xiàn)類似修改功能的接口
public interface IModificationAudited
{
DateTime? LastModificationTime { get; set; }
long? LastModifierUserId { get; set; }
}
當(dāng)更新一個實體時,ABP會自動設(shè)置這些屬性的值。你只需要在你的實體類里面實現(xiàn)這些屬性。
如果你想實現(xiàn)所有的審計屬性,你可以直接擴展 IAudited 接口;示例如下:
public interface IAudited : ICreationAudited, IModificationAudited
{
}
作為一個快速開發(fā)方式,你可以直接派生自AuditedEntity 類,不需要再去實現(xiàn)IAudited接口(AuditedEntity 類已經(jīng)實現(xiàn)了該功能,直接繼承該類就可以實現(xiàn)上述功能),AuditedEntity 類有一個實現(xiàn)不同ID數(shù)據(jù)類型的泛型版本(默認是int),可以為ID(Entity類中的ID)賦予不同的數(shù)據(jù)類型。
(2)軟刪除(Soft delete)
軟刪除是一個通用的模式被用來標記一個已經(jīng)被刪除的實體,而不是實際從數(shù)據(jù)庫中刪除記錄。例如:你可能不想從數(shù)據(jù)庫中硬刪除一條用戶記錄,因為它被許多其它的表所關(guān)聯(lián)。為了實現(xiàn)軟刪除的目的我們可以實現(xiàn)該接口 ISoftDelete:
public interface ISoftDelete{
bool IsDeleted { get; set; }
}
ABP實現(xiàn)了開箱即用的軟刪除模式。當(dāng)一個實現(xiàn)了軟刪除的實體正在被被刪除,ABP會察覺到這個動作,并且阻止其刪除,設(shè)置IsDeleted 屬性值為true并且更新數(shù)據(jù)庫中的實體。也就是說,被軟刪除的記錄不可以從數(shù)據(jù)庫中檢索出,ABP會為我們自動過濾軟刪除的記錄。(例如:Select查詢,這里指通過ABP查詢,不是通過數(shù)據(jù)庫中的查詢分析器查詢。)
如果你用了軟刪除,你有可能也想實現(xiàn)這個功能,就是記錄誰刪除了這個實體。要實現(xiàn)該功能你可以實現(xiàn)IDeletionAudited 接口,請看下面示例:
public interface IDeletionAudited : ISoftDelete
{
long? DeleterUserId { get; set; }
DateTime? DeletionTime { get; set; }
}
正如你所看到的IDeletionAudited 擴展自 ISoftDelete接口。當(dāng)一個實體被刪除的時候ABP會自動的為這些屬性設(shè)置值。
如果你想為實體類擴展所有的審計接口(例如:創(chuàng)建(creation),修改(modification)和刪除(deletion)),你可以直接實現(xiàn)IFullAudited接口,因為該接口已經(jīng)繼承了這些接口,請看下面示例:
public interface IFullAudited : IAudited, IDeletionAudited
{
}
作為一個快捷方式,你可以直接從FullAuditedEntity 類派生你的實體類,因為該類已經(jīng)實現(xiàn)了IFullAudited接口。
注意:所有的審計接口和類都有一個泛型模板為了導(dǎo)航定義屬性到你的User 實體(例如:ICreationAuditedTUser>和FullAuditedEntityTPrimaryKey, TUser>),這里的TUser指的進行創(chuàng)建,修改和刪除的用戶的實體類的類型,詳細請看源代碼(Abp.Domain.Entities.Auditing空間下的FullAuditedEntityTPrimaryKey, TUser>類),TprimaryKey 只的是Entity基類Id類型,默認是int。
(3)激活狀態(tài)/閑置狀態(tài)(Active/Passive)
有些實體需要被標記為激活狀態(tài)或者閑置狀態(tài)。那么你可以為實體采取active/passive狀態(tài)的行動?;谶@個原因而創(chuàng)建的實體,你可以擴展IPassivable 接口來實現(xiàn)該功能。該接口定義了IsActive 的屬性。
如果你首次創(chuàng)建的實體被標記為激活狀態(tài),你可以在構(gòu)造函數(shù)設(shè)置IsActive屬性值為true。
這是不同于軟刪除(IsDeleted)。如果實體被軟刪除,它不能從數(shù)據(jù)庫中被檢索到(ABP已經(jīng)過濾了軟刪除記錄)。但是對于激活狀態(tài)/閑置狀態(tài)的實體,你完全取決于你怎樣去獲取這些被標記了的實體。
IEntity接口
事實上Entity 實現(xiàn)了IEntity 接口(和EntityTPrimaryKey> 實現(xiàn)了 IEntityTPrimaryKey>接口)。如果你不想從Entity 類派生,你能直接的實現(xiàn)這些接口。其他實體類也可以實現(xiàn)相應(yīng)的接口。但是不建議你用這種方式。除非你有一個很好的理由不從Entity 類派生。
倉儲(Repositories)
倉儲定義:“在領(lǐng)域?qū)雍蛿?shù)據(jù)映射層的中介,使用類似集合的接口來存取領(lǐng)域?qū)ο蟆?Martin Fowler)。
實際上,倉儲被用于領(lǐng)域?qū)ο笤跀?shù)據(jù)庫上的操作(實體Entity和值對象Value types)。一般來說,我們針對不同的實體(或聚合根Aggregate Root)會創(chuàng)建相對應(yīng)的倉儲。
IRepository接口
在ABP中,倉儲類要實現(xiàn)IRepository接口。最好的方式是針對不同倉儲對象定義各自不同的接口。
針對Person實體的倉儲接口聲明的示例如下所示:
public interface IPersonRepository : IRepositoryPerson>
{
}
IPersonRepository繼承自IRepositoryTEntity>,用來定義Id的類型為int(Int32)的實體。如果你的實體Id數(shù)據(jù)類型不是int,你可以繼承IRepositoryTEntity, TPrimaryKey>接口,如下所示:
public interface IPersonRepository : IRepositoryPerson, long>
{
}
對于倉儲類,IRepository定義了許多泛型的方法。比如: Select,Insert,Update,Delete方法(CRUD操作)。在大多數(shù)的時候,這些方法已足已應(yīng)付一般實體的需要。如果這些方對于實體來說已足夠,我們便不需要再去創(chuàng)建這個實體所需的倉儲接口/類。在Implementation章節(jié)有更多細節(jié)。
(1)查詢(Query)
IRepository定義了從數(shù)據(jù)庫中檢索實體的常用方法。
A、取得單一實體(Getting single entity):
TEntity Get(TPrimaryKey id);
TaskTEntity> GetAsync(TPrimaryKey id);
TEntity Single(ExpressionFuncTEntity, bool>> predicate);
TEntity FirstOrDefault(TPrimaryKey id);
TaskTEntity> FirstOrDefaultAsync(TPrimaryKey id);
TEntity FirstOrDefault(ExpressionFuncTEntity, bool>> predicate);
TaskTEntity> FirstOrDefaultAsync(ExpressionFuncTEntity, bool>> predicate);
TEntity Load(TPrimaryKey id);
Get方法被用于根據(jù)主鍵值(Id)取得對應(yīng)的實體。當(dāng)數(shù)據(jù)庫中根據(jù)主鍵值找不到相符合的實體時,它會拋出例外。Single方法類似Get方法,但是它的輸入?yún)?shù)是一個表達式而不是主鍵值(Id)。因此,我們可以寫Lambda表達式來取得實體。示例如下:
var person = _personRepository.Get(42);
var person = _personRepository.Single(p => o.Name == "Halil ibrahim Kalkan");
注意,Single方法會在給出的條件找不到實體或符合的實體超過一個以上時,都會拋出例外。
FirstOrDefault也一樣,但是當(dāng)沒有符合Lambda表達式或Id的實體時,會回傳null(取代拋出異常)。當(dāng)有超過一個以上的實體符合條件,它只會返回第一個實體。
Load并不會從數(shù)據(jù)庫中檢索實體,但它會創(chuàng)建延遲執(zhí)行所需的代理對象。如果你只使用Id屬性,實際上并不會檢索實體,它只有在你存取想要查詢實體的某個屬性時才會從數(shù)據(jù)庫中查詢實體。當(dāng)有性能需求的時候,這個方法可以用來替代Get方法。Load方法在NHibernate與ABP的整合中也有實現(xiàn)。如果ORM提供者(Provider)沒有實現(xiàn)這個方法,Load方法運行的會和Get方法一樣。
ABP有些方法具有異步(Async)版本,可以應(yīng)用在異步開發(fā)模型上(見Async方法相關(guān)章節(jié))。
B、取得實體列表(Getting list of entities):
ListTEntity> GetAllList();
TaskListTEntity>> GetAllListAsync();
ListTEntity> GetAllList(ExpressionFuncTEntity, bool>> predicate);
TaskListTEntity>> GetAllListAsync(ExpressionFuncTEntity, bool>> predicate);
IQueryableTEntity> GetAll();
GetAllList被用于從數(shù)據(jù)庫中檢索所有實體。重載并且提供過濾實體的功能,如下:
var allPeople = _personRespository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive person.Age > 42);
GetAll返回IQueryableT>類型的對象。因此我們可以在調(diào)用完這個方法之后進行Linq操作。示例:
//例子一
var query = from person in _personRepository.GetAll()
where person.IsActive
orderby person.Name
select person;
var people = query.ToList();
//例子二
ListPerson> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();
如果調(diào)用GetAll方法,那么幾乎所有查詢都可以使用Linq完成。甚至可以用它來編寫Join表達式。
說明:關(guān)于IQueryableT>
當(dāng)你調(diào)用GetAll這個方法在Repository對象以外的地方,必定會開啟數(shù)據(jù)庫連接。這是因為IQueryableT>允許延遲執(zhí)行。它會直到你調(diào)用ToList方法或在forEach循環(huán)上(或是一些存取已查詢的對象方法)使用IQueryableT>時,才會實際執(zhí)行數(shù)據(jù)庫的查詢。因此,當(dāng)你調(diào)用ToList方法時,數(shù)據(jù)庫連接必需是啟用狀態(tài)。我們可以使用ABP所提供的UnitOfWork特性在調(diào)用的方法上來實現(xiàn)。注意,Application Service方法預(yù)設(shè)都已經(jīng)是UnitOfWork。因此,使用了GetAll方法就不需要如同Application Service的方法上添加UnitOfWork特性。
有些方法擁有異步版本,可應(yīng)用在異步開發(fā)模型(見關(guān)于async方法章節(jié))。
自定義返回值(Custom return value)
ABP也有一個額外的方法來實現(xiàn)IQueryableT>的延遲加載效果,而不需要在調(diào)用的方法上添加UnitOfWork這個屬性卷標。
T QueryT>(FuncIQueryableTentity>,T> queryMethod);
查詢方法接受Lambda(或一個方法)來接收IQueryableT>并且返回任何對象類型。示例如下:
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
因為是采用Lambda(或方法)在倉儲對象的方法中執(zhí)行,它會在數(shù)據(jù)庫連接開啟之后才被執(zhí)行。你可以返回實體集合,或一個實體,或一個具部份字段(注: 非Select *)或其它執(zhí)行查詢后的查詢結(jié)果集。
(2)新增(insert)
IRepository接口定義了簡單的方法來提供新增一個實體到數(shù)據(jù)庫:
TEntity Insert(TEntity entity);
TaskTEntity> InsertAsync(TEntity entity);
TPrimaryKey InsertAndGetId(TEntity entity);
TaskTPrimaryKey> InsertAndGetIdAsync(TEntity entity);
TEntity InsertOrUpdate(TEntity entity);
TaskTEntity> InsertOrUpdateAsync(TEntity entity);
TPrimaryKey InsertOrUpdateAndGetId(TEntity entity);
TaskTPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);
新增方法會新增實體到數(shù)據(jù)庫并且返回相同的已新增實體。InsertAndGetId方法返回新增實體的標識符(Id)。當(dāng)我們采用自動遞增標識符值且需要取得實體的新產(chǎn)生標識符值時非常好用。InsertOfUpdate會新增或更新實體,選擇那一種是根據(jù)Id是否有值來決定。最后,InsertOrUpdatedAndGetId會在實體被新增或更新后返回Id值。
所有的方法都擁有異步版本可應(yīng)用在異步開發(fā)模型(見關(guān)于異步方法章節(jié))
(3)更新(UPDATE)
IRepository定義一個方法來實現(xiàn)更新一個已存在于數(shù)據(jù)庫中的實體。它更新實體并返回相同的實體對象。
TEntity Update(TEntity entity);
TaskTEntity> UpdateAsync(TEntity entity);
(4)刪除(Delete)
IRepository定了一些方法來刪除已存在數(shù)據(jù)庫中實體。
void Delete(TEntity entity);
Task DeleteAsync(TEntity entity);
void Delete(TPrimaryKey id);
Task DeleteAsync(TPrimaryKey id);
void Delete(ExpressionFuncTEntity, bool>> predicate);
Task DeleteAsync(ExpressionFuncTEntity, bool>> predicate);
第一個方法接受一個現(xiàn)存的實體,第二個方法接受現(xiàn)存實體的Id。
最后一個方法接受一個條件來刪除符合條件的實體。要注意,所有符合predicate表達式的實體會先被檢索而后刪除。因此,使用上要很小心,這是有可能造成許多問題,假如果有太多實體符合條件。
所有的方法都擁有async版本來應(yīng)用在異步開發(fā)模型(見關(guān)于異步方法章節(jié))。
(5)其它方法(others)
IRepository也提供一些方法來取得數(shù)據(jù)表中實體的數(shù)量。
int Count();
Taskint> CountAsync();
int Count(ExpressionFuncTEntity, bool>> predicate);
Taskint> CountAsync(ExpressionFuncTEntity, bool>> predicate);
Long LongCount();
Tasklong> LongCountAsync();
Long LongCount(ExpressionFuncTEntity, bool>> predicate);
Tasklong> LongCountAsync(ExpressionTEntity, bool>> predicate);
所有的方法都擁有async版本被應(yīng)用在異步開發(fā)模型(見關(guān)于異步方法章節(jié))。
(6)關(guān)于異步方法(About Async methods)
ABP支持異步開發(fā)模型。因此,倉儲方法擁有Async版本。在這里有一個使用異步模型的application service方法的示例:
public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService
{
private readonly IRepositoryPerson> _personRepository;
public PersonAppService(IRepositoryPerson> personRepository)
{
_personRepository = personRepository;
}
public async TaskGetPeopleOutput> GetAllPeople()
{
var people = await _personRepository.GetAllListAsync();
return new GetPeopleOutput
{
People = Mapper.MapListPersonDto>>(people)
};
}
}
GetAllPeople方法是異步的并且使用GetAllListAsync與await保留關(guān)鍵字。
Async不是在每個ORM框架都有提供。
上例是從EF所提供的異步能力。如果ORM框架沒有提供Async的倉儲方法則它會以同步的方式操作。同樣地,舉例來說,InsertAsync操作起來和EF的新增是一樣的,因為EF會直到單元作業(yè)(unit of work)完成之后才會寫入新實體到數(shù)據(jù)庫中(DbContext.SaveChanges)。
倉儲的實現(xiàn)
ABP在設(shè)計上是采取不指定特定ORM框架或其它存取數(shù)據(jù)庫技術(shù)的方式。只要實現(xiàn)IRepository接口,任何框架都可以使用。
倉儲要使用NHibernate或EF來實現(xiàn)都很簡單。
EntityFramework
當(dāng)你使用NHibernate或EntityFramework,如果提供的方法已足夠使用,你就不需要為你的實體創(chuàng)建倉儲對象了。我們可以直接注入IRepositoryTEntity>(或IRepositoryTEntity, TPrimaryKey>)。下面的示例為application service使用倉儲對象來新增實體到數(shù)據(jù)庫:
public class PersonAppService : IPersonAppService
{
private readonly IRepositoryPerson> _personRepository;
public PersonAppService(IRepositoryPerson> personRepository)
{
_personRepository = personRepository;
}
public void CreatePerson(CreatePersonInput input)
{
person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
_personRepository.Insert(person);
}
}
PersonAppService的建構(gòu)子注入了IRepositoryPerson>并且使用其Insert方法。當(dāng)你有需要為實體創(chuàng)建一個客制的倉儲方法,那么你就應(yīng)該創(chuàng)建一個倉儲類給指定的實體。
管理數(shù)據(jù)庫連接
數(shù)據(jù)庫連接的開啟和關(guān)閉,在倉儲方法中,ABP會自動化的進行連接管理。
當(dāng)倉儲方法被調(diào)用后,數(shù)據(jù)庫連接會自動開啟且啟動事務(wù)。當(dāng)倉儲方法執(zhí)行結(jié)束并且返回以后,所有的實體變化都會被儲存, 事務(wù)被提交并且數(shù)據(jù)庫連接被關(guān)閉,一切都由ABP自動化的控制。如果倉儲方法拋出任何類型的異常,事務(wù)會自動地回滾并且數(shù)據(jù)連接會被關(guān)閉。上述所有操作在實現(xiàn)了IRepository接口的倉儲類所有公開的方法中都可以被調(diào)用。
如果倉儲方法調(diào)用其它倉儲方法(即便是不同倉儲的方法),它們共享同一個連接和事務(wù)。連接會由倉儲方法調(diào)用鏈最上層的那個倉儲方法所管理。更多關(guān)于數(shù)據(jù)庫管理,詳見UnitOfWork文件。
儲的生命周期
所有的倉儲對象都是暫時性的。這就是說,它們是在有需要的時候才會被創(chuàng)建。ABP大量的使用依賴注入,當(dāng)倉儲類需要被注入的時候,新的類實體會由注入容器會自動地創(chuàng)建。見相根據(jù)注入文件有更多信息。
倉儲的最佳實踐
對于一個T類型的實體,是可以使用IRepositoryT>。但別任何情況下都創(chuàng)建定制化的倉儲,除非我們真的很需要。預(yù)定義倉儲方法已經(jīng)足夠應(yīng)付各種案例。
假如你正創(chuàng)建定制的倉儲(可以實現(xiàn)IRepositoryTEntity>)
倉儲類應(yīng)該是無狀態(tài)的。這意味著, 你不該定義倉儲等級的狀態(tài)對象并且倉儲方法的調(diào)用也不應(yīng)該影響到其它調(diào)用?! ?br />
當(dāng)倉儲可以使用相根據(jù)注入,盡可較少或是不相根據(jù)于其它服務(wù)?!?/p>
您可能感興趣的文章:- 解析ABP框架中的數(shù)據(jù)傳輸對象與應(yīng)用服務(wù)
- ABP框架中的日志功能完全解析
- 詳解ABP框架的參數(shù)有效性驗證和權(quán)限驗證
- 詳解ABP框架中領(lǐng)域?qū)拥念I(lǐng)域事件Domain events
- 解析ABP框架中的事務(wù)處理和工作單元
- 詳解ABP框架中的數(shù)據(jù)過濾器與數(shù)據(jù)傳輸對象的使用
- 詳解ABP框架中Session功能的使用方法
- 詳解ABP框架中的日志管理和設(shè)置管理的基本配置
- ABP框架的基礎(chǔ)配置及依賴注入講解
- ABP框架的體系結(jié)構(gòu)及模塊系統(tǒng)講解
- ASP.NET樣板項目ABP框架的特性總結(jié)
- 基于ASP.NET MVC的ABP框架入門學(xué)習(xí)教程
- ABP框架中導(dǎo)航菜單的使用及JavaScript API獲取菜單的方法