1. 程式人生 > 其它 >利用程式碼生成工具Database2Sharp生成ABP VNext框架專案程式碼

利用程式碼生成工具Database2Sharp生成ABP VNext框架專案程式碼

我們在做某件事情的時候,一般需要詳細瞭解它的特點,以及內在的邏輯關係,一旦我們詳細瞭解了整個事物後,就可以通過一些輔助手段來提高我們的做事情的效率了。本篇隨筆介紹ABP VNext框架各分層專案的規則,以及結合程式碼生成工具Database2Sharp來實現專案類程式碼,專案檔案等內容的快速生成。

我們在做某件事情的時候,一般需要詳細瞭解它的特點,以及內在的邏輯關係,一旦我們詳細瞭解了整個事物後,就可以通過一些輔助手段來提高我們的做事情的效率了。本篇隨筆介紹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