利用代碼生成工具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項目的時候,更加方便高效了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM