利用代码生成工具生成基于ABP框架的代码

在前面随笔,我介绍了整个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 的内存数据库,不影响实际数据库内容。

经过我进行简化和优化处理的框架项目结构如下所示。

image

以上是VS里面解决方案的项目结构,我根据项目之间的关系,整理了一个架构的图形,如下所示。

image

上图是以字典模块为介绍, 其中橘红色的部分就是我们为各个分层需要根据数据库构建对应的类或者接口文件。

例如对于01-Core模块层,需要增加文件

image

对于03-Application.Common模块来说,需要增加DTO和应用服务层接口文件

image

而对于04-Application应用层来说,需要增加对应的接口实现文件

image

而05、06、07模块,我们不需要加入任何文件,08-Caller层加入对WebAPI的远程调用封装类,给Winform、WPF/UWP、控制台程序等调用。

image

一个模块的变化,都会导致在上面各个分层之间增加对应的文件,这样的架构确定后,我们就可以根据对应的类生成规则进行生成接口。

2)利用代码生成工具生成分层代码

在前面随笔《代码生成工具Database2Sharp的架构介绍》中,我介绍了整个代码生成工具的架构信息,因此我们用代码生成工具生成架构代码的时候,可以利用整个数据库表的信息和关系信息来处理。

image

通过整合相关的生成规则,我们可以增加对应的ABP框架代码的生成,如下代码生成工具界面所示。

image
image

最终根据根据选择数据库表信息,一键生成相关ABP架构分层代码,文件结构如下所示。

image

对比前面项目的介绍,我们可以看到各个分层的类代码是完全一致的。如对于领域层,包含了表名称标记、字段信息和引用外键的对象。

    /// <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;
            }
        }

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public DictDataApiCaller()
        {
            this.DomainName = "DictData";//指定域对象名称,用于组装接口地址
        }

    }

这些信息是根据数据库对应字段信息和关系信息进行批量生成,我们可以在这基础上进行一定的调整,以及增加自己的业务接口,那么就非常方便了。

利用代码生成工具的数据库元数据,结合模板引擎NVelocity,我们可以为我们的项目框架代码快速生成提供了一个快速有效、统一标准的生成方式,大大提高了生产效率。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容