在前面随笔,我介绍了整个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 > 0 ? pagerInfo.PageSize : 50;
var pageIndex = pagerInfo.CurrenetPageIndex > 0 ? pagerInfo.CurrenetPageIndex : 1;
this.SkipCount = pageSize * (pageIndex - 1);
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;
* 初始化图数据需要的人员节点数据
*
* @param personBaseInfoRepository
* @return
*/
@Override
public void resetNeo4jPersonBaseInfoNode(PersonBaseInfoRepository personBaseInfoRepository) {
//查询人员节点列表
List<PersonBaseInfo> personBaseInfos www.ztyLegw.cn= graphDataDao.queryPersonBaseInfo();
for (PersonBaseInfo personBaseInfo :personBaseInfos){
try {
personBaseInfoRepository.save(personBaseInfo);
}
catch (Throwable t){
//生成节点异常时,继续跳过。
continue;
}
}
}
复制代码
初始化内部外部关心数据,代码如下:
复制代码
/********************************************开始初始化节点间关系的数据**********************************************/
/**
* 初始化五大节点间的关系数据。
* @param neo4jRelationInfoRepository
* @return
*/
@Override
public void resetAllRelationInfo(Neo4jRelationInfoRepository neo4jRelationInfoRepository) {
long start = System.currentTimeMillis(www.tcgjgw.com);
//初始化人员-->工作关系,人员->学校;人员->论文;人员->住房信息4个主关系的数据。
List<Map<String,Object>>www.feironggw.cn lists = graphDataDao.queryEntityRelationInfo();
//计算内部关系信息 ,同事,校友,邻居,合作者关系。
Map<String,List<String>> schoolFriendMap = new ConcurrentHashMap<>(); //校友
Map<String,List<String>> workTogetherMap = new ConcurrentHashMap<>(); //同事
Map<String,List<String>> neighborMap = new ConcurrentHashMap<>(); //邻居
Map<String,List<String>> collaboratorMap = new ConcurrentHashMap<>();//论文合作者
for(Map<String,Object> map :lists){
String userId = "";
//获取人员信息
if(StringUtils.isNoneEmpty((String)map.get("USERID")) && StringUtils.isNoneEmpty((String)map.get("USERNAME"))){
//取工作单位字段 UNIT_ID , UNIT_NAME
if(StringUtils.isNoneEmpty((String)map.get("UNIT_ID"www.wanhaoptdL.com)) && StringUtils.isNoneEmpty((String)map.get("UNIT_NAME"))){
//插入就职关系
neo4jRelationInfoRepository.generateGraphDataRelation_JZGX((String)map.get(www.yuntianyuL.cn"USERID"),(String)map.get("UNIT_ID"),(String)map.get("USERID")+"-www.yuntianyul.com>"+(String)map.get("UNIT_www.cmylli.com ID"));
//处理同事关系
if(workTogetherMap.containsKey((String)map.get("UNIT_ID"))){
List<String> list = workTogetherMap.get((String)map.get("UNIT_ID"));
if(!list.contains(www.cmyLgw.cn (String)map.get("USERID"))){
list.add((String)map.get("USERID"));
workTogetherMap.put((String)map.get("UNIT_ID"),list);
}
}else{
List<String> lis = new ArrayList<String>();
lis.add((String)map.get("USERID"));
workTogetherMap.put((String)map.get("UNIT_ID"),lis);
/// <summary>
/// 默认构造函数
/// </summary>
public DictDataApiCaller()
{
this.DomainName = "DictData";//指定域对象名称,用于组装接口地址
}
}
复制代码
这些信息是根据数据库对应字段信息和关系信息进行批量生成,我们可以在这基础上进行一定的调整,以及增加自己的业务接口,那么就非常方便了。
利用代码生成工具的数据库元数据,结合模板引擎NVelocity,我们可以为我们的项目框架代码快速生成提供了一个快速有效、统一标准的生成方式,大大提高了生产效率。
主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
专注于Winform开发框架/混合式开发框架、Web开发框架、Bootstrap开发框架、微信门户开发框架的研究及应用。