ABP框架 - 实体

文档目录

 

本节内容:

 

实体是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会自动触发某些事件,因此你可以注册这些事件执行你需要的任何逻辑。查看事件总线文档的“预定义事件”主题,获取更多信息。

 

IEntity 接口

实质上,Entity类实现了IEntity接口(且Entity<TPrimaryKey>实现了IEntity<TPrimaryKey>)。如果你不想从Entity类继承,你可以直接实现这些接口,这些接口对于其它实体类也是适用的,但是这不是推荐的方式,除非你有一个好的理由。

 

posted @ 2016-10-24 16:07 kid1412 阅读(...) 评论(...) 编辑 收藏