1. 程式人生 > >利用程式碼生成工具生成基於ABP框架的程式碼

利用程式碼生成工具生成基於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 的記憶體資料庫,不影響實際資料庫內容。

經過我進行簡化和優化處理的框架專案結構如下所示。

以上是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;
            }
        }

        /// <summary>
        /// 預設建構函式
        /// </summary>
        public DictDataApiCaller()
        {
            this.DomainName = "DictData";//指定域物件名稱,用於組裝介面地址
        }

    }

這些資訊是根據資料庫對應欄位資訊和關係資訊進行批量生成,我們可以在這基礎上進行一定的調整,以及增加自己的業務介面,那麼就非常方便了。

利用程式碼生成工具的資料庫元資料,結合模板引擎NVelocity,我們可以為我們的專案框架程式碼快速生成提供了一個快速有效、統一標準的生成方式,大大提高了生產效率。

&n