3.1 ABP领域层 - 实体

实体是 DDD(领域驱动设计)的核心概念之一。Eric Evans 是这样描述的“很多对象不是通过它们的属性定义的,而是通过一连串的连续性事件和标识定义的”(引用领域驱动设计一书)。

译者注:对象不是通过它们的属性来下根本性的定义,而应该是通过它的线性连续性和标识性定义的。所以,实体是具有唯一标识的ID且存储在数据库中。实体通常被映射成数据库中的一个表。

3.1.1 实体类

在 ABP 中,实体继承自 Entity 类,请看下面示例:

public class Person : Entity
{     public virtual string Name { get; set; } public virtual DateTime CreationTime { get; set; } public Task() { CreationTime = DateTime.Now; }
}

我们定义一个实体类Person,并且为它定义两个属性。父类Entity具有主键属性Id。所有继承Entity类的子类都将具有主键为Id的属性。

Id数据类型可以被更改。默认是 int类型。如果你想给 Id 定义其它类型,你应该像下面示例一样来指定 Id 的类型。

public class Person : Entity<long>
{     public virtual string Name { get; set; } public virtual DateTime CreationTime { get; set; } public Task() { CreationTime = DateTime.Now; }
} 

你可以设置为 string,Guid 或者其它你想要的数据类型。
实体类重写了 equality (==) 操作符用来判断两个实体对象是否相等(主要是判断两个实体的 Id主键 是否相等)。
还定义了一个 IsTransient()方法来检测当前 Id 的值是否与指定的类型的缺省值相等。

3.1.2 聚合根

在领域驱动设计中聚合是一种模式,聚合表示的是一组领域对象(包括实体和值对象),可以被看作是一个单元。例如:订单和订单项,这都是单独的对象。但是,我们可以将订单(以及订单项)作为一个聚合来看待。

ABP不会强迫你使用聚合,你可以在你的应用中创建聚合以及聚合根。ABP定义了一个扩展自 EntityAggregateRoot 类,用来创建聚合根实体。

领域事件

聚合根定义了 DomainEvents 的集合用来产生领域事件。在当前的工作单元完成之前,这些事件被自动的触发。事实上,通过扩展 IGeneratesDomainEvents 接口,任何实体都能够产生领域事件。但是,通常(最佳实践)是在聚合根中产生领域事件。这就是为什么它被定义在聚合根中而不是实体中。

3.1.3 接口约定

在多数应用程序中,实体一般都具有像 CreationTime 的属性,用来指示该实体是什么时候被创建的。APB 提供了一些有用的接口来实现这些类似的功能。

1. 审计(Auditing)

实现 IHasCreationTime 接口。当该实体被插入到数据库时, 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 Task() { CreationTime = DateTime.Now; }
} 

ICreationAudited 扩展自 IHasCreationTime 并且该接口具有属性 CreatorUserId :

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

当保存一个新的实体时,ABP 会自动设置 CreatorUserId 的属性值为当前用户的 Id 。
你可以很容易的实现 ICreationAudited 接口,通过派生自实体类 CreationAuditedEntity。它有一个实现不同 Id主键 数据类型的泛型版本。

下面是一个为实现类似修改功能的接口:

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

当更新一个实体时,APB 会自动设置这些属性的值。你只需要在你的实体类里面实现这些属性。
如果你想实现所有的审计属性,你可以直接扩展 IAudited 接口;示例如下:

public interface IAudited : ICreationAudited, IModificationAudited
{ } 

作为一个快速开发方式,你可以直接派生自 AuditedEntity 类,不需要再去实现 IAudited 接口,AuditedEntity 类有一个实现不同 ID 数据类型的泛型版本(默认是 int)。

2. 逻辑删除(Soft delete)

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

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

ABP 实现了开箱即用的软删除模式。当一个实现了软删除的实体正在被删除, ABP 会察觉到这个动作,并且阻止其真正删除,设置 IsDeleted 属性值为 true 并且更新数据库中的实体。也就是说,被软删除的记录不可以从数据库中检索出,ABP 会为我们自动过滤软删除的记录。(例如:Select 查询,这里指通过 ABP 查询,不是通过数据库中的查询分析器查询。)

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

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

正如你所看到的 IDeletionAudited 扩展自 ISoftDelete 接口。当一个实体被删除的时候 ABP 会自动的为这些属性设置值。
如果你想为实体类扩展所有的审计接口(例如:创建(creation),修改(modification)和删除(deletion)),你可以直接实现 IFullAudited 接口,因为该接口已经继承了这些接口。
请看下面示例:

public interface IFullAudited : IAudited, IDeletionAudited
{ } 

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

  • 为了导航定义属性到你的User 实体,所有的审计接口和类都有一个泛型模板(例如: ICreationAudited\

3. 激活状态/闲置状态(Active/Passive)

有些实体需要被标记为激活状态或者闲置状态。那么你可以为实体采取 active/passive 状态的方式来实现。
基于这个原因而创建的实体,你可以扩展IPassivable 接口来实现该功能。该接口定义了 IsActive 的属性。

如果你首次创建的实体被标记为激活状态,你可以在构造函数设置 IsActive 属性值为 true。这不同于软删除(IsDeleted)。
如果实体被软删除,它不能从数据库中被检索到(ABP 已经过滤了软删除记录)。但是对于激活状态/闲置状态的实体,这完全取决于你怎样去获取这些被标记了的实体。

3.1.4 实体更改事件

当实体是被插入,更新或者删除的时候,ABP会自动的触发相应的事件。因此,你可以注册这些事件并且执行任何你需要的逻辑。详细了解请参考领域事件

3.1.5 IEntity 接口

事实上 Entity 实现了 IEntity 接口(Entity\

3.1.6 IExtendableObject 接口

在Abp中有一个接口 IExtendableObject,可以轻松的将 任意name-value数据 关联到一个实体。如下是一个简单的实体类:

public class Person : Entity, IExtendableObject
{public string Name { get; set; }public string ExtensionData { get; set; }public Person(string name){Name = name;}
}

IExtendableObject 接口中仅定定义了一个字符串属性:ExtensionData,该属性用来存储 JSON 格式的 name-value 对象。如下所示:

var person = new Person("John");person.SetData("RandomValue", RandomHelper.GetRandom(1, 1000));
person.SetData("CustomData", new MyCustomObject { Value1 = 42, Value2 = "forty-two" });

我们可以使用 SetData 方法来设置任意类型的值。如果代码是上面示例所示的话,那么 ExtensionData 的值将会是:

{"CustomData":{"Value1":42,"Value2":"forty-two"},"RandomValue":178}

我们可以使用 GetData 方法来取得任意值:

var randomValue = person.GetData<int>("RandomValue");
var customData = person.GetData<MyCustomObject>("CustomData");

在某些情况下(当你需要动态的添加额外数据到实体的时候),这个技术是非常有用的。正常情况下,应该使用正规的属性。如同这样动态使用是类型不安全且明确的。

ABP官方文档(十五)【实体】相关推荐

  1. ABP官方文档(十八)【领域服务】

    3.4 ABP领域层 - 领域服务 3.4.1 简介 领域服务(或者服务,在DDD模式中)是被用来执行领域操作或者业务规则的.Eric Evans 在他的DDD书中这样说过:一个好的Service应该 ...

  2. ABP官方文档(十六)【值对象】

    3.2 ABP领域层 - 值对象 3.2.1 简介 用来描述领域的特殊方面.且没有标识符的一个对象,叫做值对象. 实体有自己的唯一标识,而值对象是没有标识的.如果两个实体的标识是不同的,那么它们是两个 ...

  3. Nginx官方文档(十五)【HTTP之ngx_http_dav_module|ngx_http_empty_gif_module|ngx_http_f4f_module】

    ngx_http_dav_module 示例配置 指令 dav_access dav_methods create_full_put_path min_delete_depth ngx_http_da ...

  4. Gstreamer离线版官方文档(十五)

    1.GStreamer是什么? 众所周知,Microsoft's Windows和Apple's MacOS对多媒体设备.多媒体创作.播放和实时处理等方面都有很好的支持,而Linux对多媒体应用一直略 ...

  5. Axon Framework官方文档(五)

    5.Command Model 在基于CQRS的应用程序中,一个领域模型(由Eric Evans和Martin Fowler提出的概念)可以是一种非常强大的机制,它可以利用状态更改的验证和执行所涉及的 ...

  6. ABP官方文档(四十五)【集成Hangfire】

    7.2 ABP后台服务 - 集成Hangfire 7.2.1 简介 Hangfire是一个综合性的后台作业管理工具.你可以用Hangfire来替换ABP中默认实现的后台作业管理者.你可以对Hangfi ...

  7. ABP官方文档(四十四)【后台作业和后台工人】

    7.1 ABP后台服务 - 后台作业和后台工人 7.1.1 简介 ABP提供了后台作业和后台工人,来执行应用程序中的后台线程的某些任务. 7.1.2 后台作业 由于各种各样的原因,你需要后台作业以队列 ...

  8. ABP官方文档(四十九)【集成EntityFramework】

    9.1 ABP基础设施层 - 集成Entity Framework ABP可以与任何ORM框架协同工作,它内置了对EntityFramework的集成支持.本文将介绍如何在ABP中使用EntityFr ...

  9. ABP官方文档(五)【多租户】

    1.5 ABP总体介绍 - 多租户 1.5.1 什么是多租户 维基百科:"软件多租户是指一个软件架构的实例软件运行在一个服务器上,但存在多个租户.租户是一组共享一个公共的用户访问特定权限的软 ...

  10. ajax访问带token abp,ABP官方文档(三十八)【AJAX API】

    6.6 ABP表现层 - AJAX API 6.6.2.1 AJAX操作问题 现代的应用经常会使用AJAX,尤其是单页应用,几乎是和服务器通信的唯一手段,执行AJAX通常会有以下步骤: 基本上:为了执 ...

最新文章

  1. python窗口程序-窗口程序python
  2. F# 4.0于全平台发布
  3. 王义成:阿里云Redis服务助力游戏行业发展
  4. 在S4 key user tool里创建Custom Logic的后台实现
  5. 无刷新分页 jquery.pagination.js
  6. html实现动态多表单输入,使用javascript动态编辑多个相同的HTML表单
  7. vscode 本地调试和本地服务
  8. 超实用PHP函数总结整理
  9. css3 animate基本属性
  10. BZOJ——T 1612: [Usaco2008 Jan]Cow Contest奶牛的比赛
  11. 推荐7 款实用好用的电脑软件
  12. AS更换背景主题以及背景图片
  13. 电脑注册表怎么打开?
  14. python爬虫-基础入门-python爬虫突破封锁
  15. 计算机二级考试题库 操作题,2016计算机二级考试题库:《C++》基本操作题练习...
  16. html免费问答系统模板,tipask问答系统模板文件对照表详解
  17. js vue+elementui 全屏跟退出全屏功能搬砖
  18. Spring Security优劣之我见
  19. linux C之alarm函数(更改)
  20. 发布H5时,提示文件查找失败

热门文章

  1. 玩转wsl2之搭建android代码仓库
  2. ubuntu18.04 安装pcl点云库(亲测有效)
  3. python数据分析三剑客之pandas
  4. 美国《商业周刊》:从“IT 100强”解析未来趋势
  5. WPF的货物托运界面
  6. 非常郁闷的一次集体活动!
  7. 你知道自己需要多大带宽的服务器吗
  8. 计算机软件无形资产机械工具,​服务器属于固定资产还是无形资产
  9. 【我写过最蠢的代码是】尊嘟假嘟 O.o
  10. 2022年Java求职经历