• 利用代码生成工具Database2Sharp生成ABP VNext框架项目代码


    我们在做某件事情的时候,一般需要详细了解它的特点,以及内在的逻辑关系,一旦我们详细了解了整个事物后,就可以通过一些辅助手段来提高我们的做事情的效率了。本篇随笔介绍ABP VNext框架各分层项目的规则,以及结合代码生成工具Database2Sharp来实现项目类代码,项目文件等内容的快速生成。

    ABP VNext框架在官方下载项目的时候,会生成一个标准的空白项目框架,本代码工具不是替代这个项目代码生成,而是基于这个基础上进行基于数据表的增量式开发模块的需求(毕竟官方没有针对数据表的项目代码生成),最终所有的子模块可以集成在主模块上,形成一个完整的系统。

    1、ABP VNext框架的项目关系

    目前框架代码生成包括:应用服务层:Application.Contracts和Application项目,领域层:Domain.Shared和Domain项目,基础设施层:EntityFrameworkCore项目,HTTP 层:HttpApi和HttpApi.Client项目。生成代码集成相关的基类代码,简化项目文件的类代码。

    应用服务层:

    Application.Contracts,包含应用服务接口和相关的数据传输对象(DTO)。
    Application,包含应用服务实现,依赖于 Domain 包和 Application.Contracts 包。

    领域层:
    Domain.Shared,包含常量,枚举和其他类型.
    Domain 包含实体, 仓储接口,领域服务接口及其实现和其他领域对象,依赖于 Domain.Shared 包.

    基础设施层:

    EntityFrameworkCore,包含EF的ORM处理,使用仓储模式,实现数据的存储功能。

    HTTP 层
    HttpApi项目, 为模块开发REST风格的HTTP API。
    HttpApi.Client项目,它将应用服务接口实现远程端点的客户端调用,提供的动态代理HTTP C#客户端的功能。

    各个层的依赖关系如下图所示。

    2、ABP VNext框架各层的项目代码

    我在上篇随笔《在ABP VNext框架中对HttpApi模块的控制器进行基类封装》中介绍了为了简化子类一些繁复代码的重复出现,使用自定义基类方式,封装了一些常用的函数,通过泛型参数的方式,可以完美的实现强类型接口的各种处理。

    对于ABP VNext个项目的内容,我们继续推演到它的项目组织上来。为了简便,我们以简单的客户表T_Customer表来介绍框架项目的分层和关系。

    对于这个额外添加的表,首先我们来看看应用服务层的Application.Contracts项目文件,如下所示。

    其中映射DTO放在DTO目录中,而应用服务的接口定义则放在Interface目录中,使用目录的好处是利于查看和管理,特别是在业务表比较多的情况下。

    DTO类的定义如下所示。

    其中用到了基类对象EntityDto、CreationAuditedEntityDto、AuditEntityDto、FullAuditedEntityDto几个基类DTO对象,具体采用哪个基类DTO,依赖于我们表的包含哪些系统字段。如只包含CreationTime、CreatorId那么就采用CreationAuditedEntityDto,其他的依次类推。

    领域层的实体类关系和前面DTO关系类似,如下所示。

    这样我们利用代码生成工具生成代码的时候,就需要判断表的系统字段有哪些来使用不同的系统DTO基类了。

    而应用服务层的接口定义文件如下所示,它使用了我们前面随笔介绍过的自定义基类或接口。

    通过传入泛型类型,我们可以构建强类型化的接口定义。 

    应用服务层的Application项目包含DTO映射文件和应用服务层接口实现类,如下所示。

     

    其中映射DTO、Domain Entity(领域实体)关系的Automapper文件放在MapProfile文件夹中,而接口实现类文件则放在Service目录中,也是方便管理。

    映射类文件,主要定义DTO和Domain Entity(领域实体)关系,如下所示。

     这样文件单独定义,在模块中会统一加载整个程序集的映射文件,比较方便。

        public class TestProjectApplicationModule : AbpModule
        {
            public override void ConfigureServices(ServiceConfigurationContext context)
            {
                Configure<AbpAutoMapperOptions>(options =>
                {
                    options.AddMaps<TestProjectApplicationModule>();
                });
            }
        }

    应用服务层的接口实现如下定义所示。

        /// <summary>
        /// Customer,应用层服务接口实现
        /// </summary>
        public class CustomerAppService : 
            MyCrudAppService<Customer, CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>, 
            ICustomerAppService

    通过继承相关的自定义基类,可以统一封装一些常见的接口实现,传入对应的泛型类型,可以构建强类型的接口实现。

    另外实现类还需要包含一些方法的重载,以重写某些规则,如排序、查询处理、以及一些通用的信息转义等,详细的应用服务层接口实现代码如下所示。

        /// <summary>
        /// Customer,应用层服务接口实现
        /// </summary>
        public class CustomerAppService : 
            MyCrudAppService<Customer, CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>, 
            ICustomerAppService
        {
            private readonly IRepository<Customer, string> _repository;//业务对象仓储对象
    
            public CustomerAppService(IRepository<Customer, string> repository) : base(repository)
            {
                _repository = repository;
            }
    
            /// <summary>
            /// 自定义条件处理
            /// </summary>
            /// <param name="input">查询条件Dto</param>
            /// <returns></returns>
            protected override async Task<IQueryable<Customer>> CreateFilteredQueryAsync(CustomerPagedDto input)
            {
                var query = await base.CreateFilteredQueryAsync(input);
                query = query
                    .WhereIf(!input.ExcludeId.IsNullOrWhiteSpace(), t=>t.Id != input.ExcludeId) //不包含排除ID
                    .WhereIf(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精确匹配则用Equals
                     //区间查询
                    .WhereIf(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
                    .WhereIf(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value)
     
                    //创建日期区间查询
                    .WhereIf(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
                    .WhereIf(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value)
                    ;            
    
                return query;
            }
    
            /// <summary>
            /// 自定义排序处理
            /// </summary>
            /// <param name="query">可查询LINQ</param>
            /// <param name="input">查询条件Dto</param>
            /// <returns></returns>
            protected override IQueryable<Customer> ApplySorting(IQueryable<Customer> query, CustomerPagedDto input)
            {
                //按创建时间倒序排序
                return base.ApplySorting(query, input).OrderByDescending(s => s.CreationTime);//时间降序
                //先按第一个字段排序,然后再按第二字段排序
                //return base.ApplySorting(query, input).OrderBy(s=>s.DictType_ID).ThenBy(s => s.Seq);
            }
    
            /// <summary>
            /// 获取字段中文别名(用于界面显示)的字典集合
            /// </summary>
            /// <returns></returns>
            public override Task<Dictionary<string, string>> GetColumnNameAlias()
            {
                Dictionary<string, string> dict = new Dictionary<string, string>();
                #region 添加别名解析
                //系统部分字段
                dict.Add("Id", "编号");
                dict.Add("UserName", "用户名");
                dict.Add("Creator", "创建人");
                dict.Add("CreatorUserName", "创建人");
                dict.Add("CreationTime", "创建时间");
    
                //其他字段
                 dict.Add("Name", "姓名");
                 dict.Add("Age", "");
               
                #endregion
    
                return Task.FromResult(dict);
            }
    
            /// <summary>
            /// 对记录进行转义
            /// </summary>
            /// <param name="item">dto数据对象</param>
            /// <returns></returns>
            protected override void ConvertDto(CustomerDto item)
            {
                //如需要转义,则进行重写
    
                #region 参考代码
                //用户名称转义
                //if (item.Creator.HasValue)
                //{
                //    //需在CustomerDto中增加CreatorUserName属性
                //    var user = _userRepository.FirstOrDefault(item.Creator.Value);
                //    if (user != null)
                //    {
                //        item.CreatorUserName = user.UserName;
                //    }
                //}
                //if (item.UserId.HasValue)
                //{
                //    item.UserName = _userRepository.Get(item.UserId.Value).UserName;
                //}
    
                //IP地址转义
                //if (!string.IsNullOrEmpty(item.ClientIpAddress))
                //{
                //    item.ClientIpAddress = item.ClientIpAddress.Replace("::1", "127.0.0.1");
                //} 
                #endregion
            }
    
            /// <summary>
            /// 用于测试的额外接口
            /// </summary>
            public Task<bool> TestExtra()
            {
                return Task.FromResult(true);
            }
        }

    这些与T_Customer 表相关的信息,如表信息,字段信息等相关的内容,可以通过代码生成工具元数据进行统一处理即可。

    领域层的内容,包含Domain、Domain.Share两个项目,内容和Applicaiton.Contracts项目类似,主要定义一些实体相关的内容,这部分也是根据表和表的字段进行按规则生成。而其中一些类则根据命名控件和项目名称构建即可。

     而Domain项目中的Customer领域实体定义代码如下所示。

    而领域实体和聚合根的基类关系如下所示。

     

    具体使用***Entity(如FullAuditedEntity基类)还是使用聚合根***AggregateRoot(如FullAuditedAggregateRoot)作为领域实体的基类,生成的时候,我们需要判断表的字段关系即可,如果表包含ExtraProperties和ConcurrencyStamp,则使用聚合根相关的基类。

    我们的T_Customer包含聚合根基类所需要的字段,代码生成的时候,则基类应该使用FullAuditedAggregateRoot<T>基类。

    对于EntityFrameworkCore项目文件,它主要就是生成对应表的DbSet然后用于操作即可。

    其中DbContext文件如下所示

    namespace WHC.TestProject.EntityFrameworkCore
    {
        [ConnectionStringName("Default")]
        public class TestProjectDbContext : AbpDbContext<TestProjectDbContext>
        {
            /// <summary>
            /// T_Customer,数据表对象
            /// </summary>
            public virtual DbSet<Customer> Customers { get; set; }
    
            public TestProjectDbContext(DbContextOptions<TestProjectDbContext> options)
                : base(options)
            {
            }
    
            protected override void OnModelCreating(ModelBuilder builder)
            {
                base.OnModelCreating(builder);
                builder.ConfigureTestProject();
            }
        }
    }

    按照规则生成即可,其他的可以不管了。

    我们注意一下EntityFrameworkCoreModule中的内容处理,如果不是采用聚合根作为领域实体的基类,而是采用**Entity标准实体(如FullAuditedEntity基类)作为基类,那么需要在该文件中默认设置为true处理,因为ABP VNext框架默认只是加入聚合根的领域实体处理。

    namespace WHC.TestProject.EntityFrameworkCore
    {
        [DependsOn(
            typeof(TestProjectDomainModule),
            typeof(AbpEntityFrameworkCoreModule)
        )]
        public class TestProjectEntityFrameworkCoreModule : AbpModule
        {
            public override void ConfigureServices(ServiceConfigurationContext context)
            {
                context.Services.AddAbpDbContext<TestProjectDbContext>(options =>
                {
                    //options.AddDefaultRepositories();
    
                    //默认情况下,这将为每个聚合根实体(从派生的类AggregateRoot)创建一个存储库。
                    //如果您也想为其他实体创建存储库,请设置includeAllEntities为true:
                    //参考https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories
                    options.AddDefaultRepositories(includeAllEntities: true);
                });
            }
        }
    }

    上面的处理,即使是采用**Entity标准实体(如FullAuditedEntity基类)作为基类,也没问题了,可以顺利反射Repository对象出来了。

    而对于Http项目分层,包含的HttpApi项目和HttpApi.Client,前者是重从应用服务层的服务接口,使用知道自定义的API规则,虽然默认可以使用应用服务层ApplicationService的自动API发布,不过为了更强的控制规则,建议重写(也是官方的做法)。两个项目文件如下所示。

    其中HttpApi项目控制器,也是采用了此前介绍过的自定义基类,可以减少重复代码的处理。

    namespace WHC.TestProject.Controllers
    {
        /// <summary>
        /// Customer,控制器对象
        /// </summary>
        //[RemoteService]
        //[Area("crm")]
        [ControllerName("Customer")]
        [Route("api/Customer")]
        public class CustomerController : 
            MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, 
            ICustomerAppService
        {
            private readonly ICustomerAppService _appService;
    
            public CustomerController(ICustomerAppService appService) : base(appService)
            {
                _appService = appService;
            }
    
        }
    }

    其中MyAbpControllerBase控制器基类,封装了很多常见的CRUD方法(Create/Update/Delete/GetList/Get),以及一些BatchDelete、Count、GetColumnNameAlias等基础方法。

    对于ABP VNext各个项目的项目文件的生成,我们这里顺便说说,其实这个文件很简单,没有太多的内容,包含命名空间,项目名称,以及一些常见的引用而已,它本身也是一个XML文件,填入相关信息生成文件即可。

    而对于解决方案,它就是包含不同的项目文件,以及各个项目文件有一个独立的GUID,因此我们动态构建对应的GUID值,然后绑定在模板上即可。

     代码工具中,后端提供数据绑定到前端模板即可实现内容的动态化了。

     

     3、使用代码生成工具Database2Sharp生成ABP VNext框架项目

    上面介绍了ABP VNext框架的各个项目层的代码生成,以及一些代码生成处理的规则,那么实际的代码生成工具生成是如何的呢?

    代码生成工具下载地址:http://www.iqidi.com/database2sharp.htm。 

    首先单击左侧节点展开ABP VNext项目的数据库,让数据库的元数据读取出来,便于后面的代码生成。

     然后从右键菜单中选择【代码生成】【ABP VNext框架代码生成】或者工具栏中选择快速入口,一样的效果。

    在弹出的对话框中选择相关的数据表,用于生成框架代码即可,注意修改合适的主命名空间,可以是TestProject或者WHC.Project等类似的名称。

     

     最后下一步生成确认即可生成相关的解决方案代码。

    生成后所有项目关系已经完善,可以直接打开解决方案查看到整个项目情况如下所示。

    这样生成的解决方案,可以编译为一单独的模块,需要的时候,直接在主项目中引用并添加依赖即可。

    例如我们在一个ABP VNext的标准项目MicroBookStore.Web中引入刚才代码生成工具生成的模块,那么在MicroBookStoreWebModule.cs 中添加依赖即可,如下所示。

     由于我们是采用DLL的引用方式,那么在项目添加对应的引用关系才可以使用。

     同时在EFCore项目中添加项目的TestProject项目的EF依赖关系如下所示。

    这样跑动起来项目,就可以有Swagger的接口可以查看并统一调用了。

    以上就是对于ABP VNext框架项目的分析和项目关系的介绍,并重要介绍利用代码生成工具来辅助增量式模块化开发的操作处理,这样我们在开发ABP VNext项目的时候,更加方便高效了。

    主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
    专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
      转载请注明出处:
    撰写人:伍华聪  http://www.iqidi.com 
        
  • 相关阅读:
    Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
    zookeeper 是如何保证事务的顺序一致性的?
    什么是 Callable 和 Future?
    MyBatis 实现一对一有几种方式?具体怎么操作的?
    Zookeeper 对节点的 watch监听通知是永久的吗?为什么 不是永久的?
    合同测试你懂什么?
    查看各类环境变量用什么命令?
    我们如何进行跨功能测试?
    怎么查看系统支持的所有信号?
    Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/15783620.html
Copyright © 2020-2023  润新知