基于DDD的.NET开发框架 - ABP的Entity设计思想

时间:2022-05-14 14:33:44

返回ABP系列

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。

ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。

ABP的官方网站:http://www.aspnetboilerplate.com

ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents

Github上的开源项目:https://github.com/aspnetboilerplate

一、基本概念

实体是DDD(领域驱动设计)的核心概念之一。Eirc Evans是这样描述的实体的:“它根本上不是通过属性定义的,而是通过一系列连续性和标识定义的”。因此,实体都有Id属性并且都存储到数据库中。一个实体一般会映射到数据库的一张表。

1、实体类

在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属性,它是该Entity类的主键。因此,所有实体的主键名都是相同的,都是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;
}
}

主键Id也可以设置为string,Guid或其他类型的。

Entity类重写了等号运算符(==),可以轻松地检查两个实体是否相同了(实体的Id相同则认为它们相同)。它也定义了IsTransient方法来检测它是否有Id。

2、接口约定

在很多应用程序中,很多实体具有像CreationTime的属性(数据库表也有该字段)用来指示该实体是什么时候被创建的。APB提供了一些有用的接口来实现这些类似的功能。也就是说,为这些实现了这些接口的实体,提供了一个通用的编码方式(通俗的说只要实现指定的接口就能实现指定的功能)。

1)、审计

实体类实现 IHasCreationTime 接口就可以具有CreationTime的属性。当该实体被插入到数据库时, ABP会自动设置该属性的值为当前时间。

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会自动地将当前的用户Id设置为CreatorUserId。

你也可以通过从CreationAuditedEntity类派生实体,从而轻易地实现ICreationAudited。

对于修改也有相似的接口:

public interface IModificationAudited
{
DateTime? LastModificationTime { get; set; } long? LastModifierUserId { get; set; }
}

作为一个快捷方式,你可以从AuditedEntity类派生,而不需要直接实现IAudited。AuditedEntity类对于不同类型的Id属性也有泛型的版本。

2)、软删除

软删除是一个通用的模式被用来标记一个已经被删除的实体,而不是实际从数据库中删除记录。例如:你可能不想从数据库中硬删除一条用户记录,因为它被许多其它的表所关联。为了实现软删除的目的我们可以实现该接口 ISoftDelete:

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

ABP实现了开箱即用的软删除模式。当一个软删除实体被删除后,ABP检测到之后,会阻止删除,将IsDeleted设置为true并更新数据库中的实体。而且,它会自动地过滤数据库中软删除的实体,不会检索(select)它们。

如果你用了软删除,你有可能也想实现这个功能,就是记录谁删除了这个实体。要实现该功能你可以实现IDeletionAudited 接口,请看下面示例:

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

IDeletionAudited扩展了ISoftDelete,当删除一个实体时,ABP会自动设置这些属性。

如果你想为一个实体实现所有的审计接口(创建,修改和删除),那么可以直接实现IFullAudited,因为它继承了所有的这些接口:

public interface IFullAudited : IAudited, IDeletionAudited
{ }

作为一个快捷方式,你可以直接从FullAuditedEntity 类派生你的实体类,因为该类已经实现了IFullAudited接口。

注意:所有的审计接口和类都有一个定义导航属性到User实体的泛型版本(比如ICreationAudited和FullAuditedEntity<tprimarykey,tuser>)。

3)、激活/未激活

一些实体需要标记为激活的或未激活的。这样,你就可以根据实体的激活或者未激活状态来采取行动。你可以实现IPassivable接口来达到目的。该接口定义了IsActive属性。

如果实体在第一次创建时是激活的,那么你可以在构造函数中将IsActive设置为true。

这与软删除(IsDeleted)是不同的。如果一个实体是软删除的,那么它就不会从数据库中检索到了(ABP默认会阻止),但是,对于激活或者未激活的实体,控制获取实体完全取决于你。

2、IEntity接口

实际上,Entity类实现了IEntity接口(且Entity实现了IEntity)。如果不想从Entity类中派生,那么可以直接实现这些接口。但是,除非你有一个好的原因不从Entity类派生,否则,不建议这么做。

二、ABP源码分析

代码:

基于DDD的.NET开发框架 - ABP的Entity设计思想

部分类图:

基于DDD的.NET开发框架 - ABP的Entity设计思想

基于DDD的.NET开发框架 - ABP的Entity设计思想

IEntity<TPrimaryKey>: 封装了PrimaryKey:Id,这是一个泛型类型

IEntity: 封装了PrimaryKey:Id,这是一个int类型

Entity<TPrimaryKey> :支持主键是泛型类型的Entity

Entity:支持主键是int类型的Entity

IHasCreationTime: 封装了CreationTime

ICreationAudited: 封装了CreatorUserId,这个是long类型

CreationAuditedEntity<TPrimaryKey> : 支持主键是泛型类型的Entity,并且封装了CreationTime 和 CreatorUserId

CreationAuditedEntity: 只支持主键是int类型的Entity,并且封装了CreationTime 和 CreatorUserId

ICreationAudited<TUser> :封装了泛型类型的creator

CreationAuditedEntity<TPrimaryKey, TUser> :  支持主键是泛型类型,并且封装了泛型类型的creator的Entity

ISoftDelete:封装了软删除的标志IsDeleted

IHasDeletionTime:封装了DeletionTime

IDeletionAudited:封装了DeleterUserId,这个是long类型

IDeletionAudited: 封装了泛型类型的DeleterUser

IHasModificationTime:封装了LastModificationTime

IModificationAudited: 封装了LastModifierUserId,这个是long类型

IModificationAudited<TUser> :  封装了泛型类型的LastModifierUser

IAudited:从其父类接口那继承了Creation 和 Modification 的时间和UserID,这个是long类型

AuditedEntity<TPrimaryKey> :支持主键是泛型类型的Entity,并且从其父类接口那继承了Creation 和 Modification 的时间和UserID,这个是long类型

AuditedEntity: 与AuditedEntity<TPrimaryKey>的区别就是其只支持主键是int类型的Entity。

AuditedEntity<TPrimaryKey, TUser> :与AuditedEntity<TPrimaryKey>的区别就是其支持泛型类型的LastModifierUser和CreatorUser。

IAudited:从其父类接口那继承了Creation,Modification和delete 的时间和UserID,这个是long类型

FullAuditedEntity<TPrimaryKey> :支持主键是泛型类型的Entity,并且从其父类接口那继承了Creation,Modification和delete 的时间和UserID,这个是long类型

FullAuditedEntity:与FullAuditedEntity<TPrimaryKey>的区别就是其只支持主键是int类型的Entity

FullAuditedEntity<TPrimaryKey, TUser> :与FullAuditedEntity<TPrimaryKey>的区别就是其支持泛型类型的LastModifierUser,CreatorUser 和 deleteuser。