在前面随笔,我介绍了整个ABP优化过框架的分层模型,包括尽量简化整个ABP框架的各个层的关系,以及纳入一些基类的辅助处理,使得我们对应业务分层类或者接口尽可能减少代码,并具有生产环境所需要的基类接口,通过我对整个ABP框架模型的分析,我们可以结合代码生成工具Database2Sharp来生成对应分层的代码,该工具后台具备数据库表所需要的一切字段信息和关系信息,因此我们确定好逻辑关系就可以生成对应分层的代码。本篇随笔介绍代码生成工具Database2Sharp生成基于ABP框架的分层代码过程。
1)ABP框架回顾
ABP框架主要还是基于领域驱动的理念来构建整个架构的,其中领域驱动包含的概念有 域对象Entities、仓储对象Repositories、域服务接口层Domain Services、域事件Domain Events、应用服务接口Application Services、数据传输对象DTOs等。
以下是ABP初始框架的各个分层的信息,它主要是分为下面几个项目分层。
Application应用层:应用层提供一些应用服务(Application Services)方法供展现层调用。一个应用服务方法接收一个DTO(数据传输对象)作为输入参数,使用这个输入参数执行特定的领域层操作,并根据需要可返回另一个DTO。
Core领域核心层,领域层就是业务层,是一个项目的核心,所有业务规则都应该在领域层实现。这个项目里面,除了定义所需的领域实体类外,其实可以定义我们自己的自定义的仓储对象(类似DAL/IDAL),以及定义自己的业务逻辑层(类似BLL/IBLL),以及基于AutoMapper映射规则等内容。
EntityFrameworkCore 实体框架核心层,这个项目不需要修改太多内容,只需要在DbContext里面加入对应领域对象的仓储对象即可。
Migrator数据迁移层,这个是一个辅助创建的控制台程序项目,如果基于DB First,我们可以利用它来创建我们项目的初始化数据库。
Web.Core Web核心层,基于Web或者Web API的核心层,提供了对身份登陆验证的基础处理,没有其他内容。
Web.Core.Host Web API的宿主层,也是动态发布Web API的核心内容,另外在Web API里面整合了Swagger,使得我们可以方便对Web API的接口进行调试。
Tests 单元测试层,这个提供了一些应用层对象的模拟测试,其中测试的数据库使用的是Entity Framework 的内存数据库,不影响实际数据库内容。
经过我进行简化和优化处理的框架项目结构如下所示。
以上是VS里面解决方案的项目结构,我根据项目之间的关系,整理了一个架构的图形,如下所示。
上图是以字典模块为介绍, 其中橘红色的部分就是我们为各个分层需要根据数据库构建对应的类或者接口文件。
例如对于01-Core模块层,需要增加文件
对于03-Application.Common模块来说,需要增加DTO和应用服务层接口文件
而对于04-Application应用层来说,需要增加对应的接口实现文件
而05、06、07模块,我们不需要加入任何文件,08-Caller层加入对WebAPI的远程调用封装类,给Winform、WPF/UWP、控制台程序等调用。
一个模块的变化,都会导致在上面各个分层之间增加对应的文件,这样的架构确定后,我们就可以根据对应的类生成规则进行生成接口。
2)利用代码生成工具生成分层代码
在前面随笔《代码生成工具Database2Sharp的架构介绍》中,我介绍了整个代码生成工具的架构信息,因此我们用代码生成工具生成架构代码的时候,可以利用整个数据库表的信息和关系信息来处理。
通过整合相关的生成规则,我们可以增加对应的ABP框架代码的生成,如下代码生成工具界面所示。
最终根据根据选择数据库表信息,一键生成相关ABP架构分层代码,文件结构如下所示。
对比前面项目的介绍,我们可以看到各个分层的类代码是完全一致的。如对于领域层,包含了表名称标记、字段信息和引用外键的对象。
/// <summary>
/// 通用字典明细项目信息,领域对象
/// </summary>
[Table("TB_DictData")]
public class DictData : FullAuditedEntity<string>
{
/// <summary>
/// 默认构造函数(需要初始化属性的在此处理)
/// </summary>
public DictData()
{
} #region Property Members /// <summary>
/// 字典大类
/// </summary>
//[Required]
public virtual string DictType_ID { get; set; } /// <summary>
/// 字典名称
/// </summary>
//[Required]
public virtual string Name { get; set; } /// <summary>
/// 字典值
/// </summary>
public virtual string Value { get; set; } /// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; } /// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; } /// <summary>
/// 字典大类
/// </summary>
[ForeignKey("DictType_ID")]
public virtual DictType DictType { get; set; }
#endregion }
对于DTO文件,我们看看代码信息
/// <summary>
/// 通用字典明细项目信息,DTO对象
/// </summary>
public class DictDataDto
{
/// <summary>
/// 默认构造函数(需要初始化属性的在此处理)
/// </summary>
public DictDataDto()
{
} #region Property Members /// <summary>
/// 字典大类
/// </summary>
public virtual string DictType_ID { get; set; } /// <summary>
/// 字典名称
/// </summary>
public virtual string Name { get; set; } /// <summary>
/// 字典值
/// </summary>
//[Required]
public virtual string Value { get; set; } /// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; } /// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; } #endregion } /// <summary>
/// 创建通用字典明细项目信息,DTO对象
/// </summary>
public class CreateDictDataDto : DictDataDto
{
} /// <summary>
/// 用于根据条件分页查询,DTO对象
/// </summary>
public class DictDataPagedDto : PagedResultRequestDto
{
public DictDataPagedDto() { } /// <summary>
/// 参数化构造函数
/// </summary>
/// <param name="skipCount">跳过的数量</param>
/// <param name="resultCount">最大结果集数量</param>
public DictDataPagedDto(int skipCount, int resultCount)
{
this.SkipCount = skipCount;
this.MaxResultCount = resultCount;
} /// <summary>
/// 使用分页信息进行初始化SkipCount 和 MaxResultCount
/// </summary>
/// <param name="pagerInfo">分页信息</param>
public DictDataPagedDto(PagerInfo pagerInfo)
{
if (pagerInfo != null)
{
//默认设置
var pageSize = pagerInfo.PageSize > ? pagerInfo.PageSize : ;
var pageIndex = pagerInfo.CurrenetPageIndex > ? pagerInfo.CurrenetPageIndex : ; this.SkipCount = pageSize * (pageIndex - );
this.MaxResultCount = pageSize;
}
} #region Property Members /// <summary>
/// 字典大类
/// </summary>
public virtual string DictType_ID { get; set; } /// <summary>
/// 字典名称
/// </summary>
public virtual string Name { get; set; } /// <summary>
/// 字典值
/// </summary>
public virtual string Value { get; set; } /// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; } /// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; } #endregion
}
DTO的映射文件代码生成如下
/// <summary>
/// 通用字典明细项目信息,映射文件
/// </summary>
public class DictDataMapProfile : Profile
{
public DictDataMapProfile()
{
CreateMap<DictDataDto, DictData>();
CreateMap<DictData, DictDataDto>();
CreateMap<CreateDictDataDto, DictData>();
}
}
应用服务层接口实现代码如下所示。
/// <summary>
/// 通用字典明细项目信息,应用层服务接口实现
/// </summary>
[AbpAuthorize]
public class DictDataAppService : MyAsyncServiceBase<DictData, DictDataDto, string, DictDataPagedDto, CreateDictDataDto, DictDataDto>, IDictDataAppService
{
private readonly IRepository<DictData, string> _repository; public DictDataAppService(IRepository<DictData, string> repository) : base(repository)
{
_repository = repository;
} /// <summary>
/// 自定义条件处理
/// </summary>
/// <param name="input">查询条件Dto</param>
/// <returns></returns>
protected override IQueryable<DictData> CreateFilteredQuery(DictDataPagedDto input)
{
return base.CreateFilteredQuery(input)
.WhereIf(!DictType_ID.IsNullOrWhiteSpace(), t => t.DictType_ID.Contains(input.DictType_ID))
.WhereIf(!Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name))
.WhereIf(!Value.IsNullOrWhiteSpace(), t => t.Value.Contains(input.Value))
.WhereIf(!Remark.IsNullOrWhiteSpace(), t => t.Remark.Contains(input.Remark))
.WhereIf(!Seq.IsNullOrWhiteSpace(), t => t.Seq.Contains(input.Seq));
} /// <summary>
/// 自定义排序处理
/// </summary>
/// <param name="query">可查询LINQ</param>
/// <param name="input">查询条件Dto</param>
/// <returns></returns>
protected override IQueryable<DictData> ApplySorting(IQueryable<DictData> query, DictDataPagedDto input)
{
return base.ApplySorting(query, input); //示例代码
//先按字典类型排序,然后同一个字典类型下的再按Seq排序
//return base.ApplySorting(query, input).OrderBy(s=>s.DictType_ID).ThenBy(s => s.Seq);
}
}
ApiCaller分层的代码实现如下所示。
/// <summary>
/// 通用字典明细项目信息的Web API调用处理
/// </summary>
public class DictDataApiCaller : AsyncCrudApiCaller<DictDataDto, string, DictDataPagedDto, CreateDictDataDto, DictDataDto>, IDictDataAppService
{
/// <summary>
/// 提供单件对象使用
/// </summary>
public static DictDataApiCaller Instance
{
get
{
return Singleton<DictDataApiCaller>.Instance;
}
} /// <summary>
/// 默认构造函数
/// </summary>
public DictDataApiCaller()
{
this.DomainName = "DictData";//指定域对象名称,用于组装接口地址
} }
这些信息是根据数据库对应字段信息和关系信息进行批量生成,我们可以在这基础上进行一定的调整,以及增加自己的业务接口,那么就非常方便了。
利用代码生成工具的数据库元数据,结合模板引擎NVelocity,我们可以为我们的项目框架代码快速生成提供了一个快速有效、统一标准的生成方式,大大提高了生产效率。