基於SqlSugar的開發框架循序漸進介紹(3)– 實現程式碼生成工具Database2Sharp的整合開發

我喜歡在一個項目開發模式成熟的時候,使用程式碼生成工具Database2Sharp來配套相關的程式碼生成,對於我介紹的基於SqlSugar的開發框架,從整體架構確定下來後,我就著手為它們量身定做相關的程式碼開發,這樣可以在後續整合項目功能的時候,利用程式碼生成工具快速的生成所需要模組的骨架程式碼,然後在這個基礎上逐漸增加自定義的內容即可,方便快捷。本篇隨筆介紹基於SqlSugar的開發框架,對框架中涉及到的各個分層或者模組程式碼進行生成的處理。

1、回顧項目的架構和模組內容

在前面幾篇隨筆中,大概介紹過了基於SqlSugar的開發框架主要的設計模組,場景如下所示。

基礎核心數據模組SugarProjectCore,主要就是開發業務所需的數據處理和業務邏輯的項目,為了方便,我們區分Interface、Modal、Service三個目錄來放置不同的內容,其中Modal是SqlSugar的映射實體,Interface是定義訪問介面,Service是提供具體的數據操作實現。其中Service裡面一些框架基類和介面定義,統一也放在公用類庫裡面。

Winform介面,我們可以採用基於.net Framework開發或者.net core6進行開發均可,因為我們的SugarProjectCore項目是採用.net Standard模式開發,兼容兩者。

這裡以許可權模組來進行演示整合使用,我在構建程式碼生成工具程式碼模板的時候,反覆利用項目中測試沒問題的項目程式碼指導具體的模板編寫,這樣編寫出來的模板就會完美符合實際的項目需要了。

在項目程式碼及模板完成後,利用程式碼生成工具快速生成程式碼,相互促進情況下,也完成了Winform項目的介面程式碼生成,生成包括普通的列表介面,以及主從表Winform介面程式碼生成。

最後許可權系統的Winform項目如下所示。

在前面隨筆《基於SqlSugar的開發框架循序漸進介紹(2)– 基於中間表的查詢處理》中介紹了基礎功能的一些處理,其中也介紹到了Winform介面端的介面效果,這個以SqlSugar底層處理,最終把許可權、字典等模組整合到一起,完成一個項目開發所需要的框架結構內容。整個系統包括用戶管理、組織機構管理、角色管理、功能許可權管理、菜單管理、欄位許可權管理、黑白名單、操作日誌、字典管理、客戶資訊等模組內容。

在程式碼生成工具中,我們整合了基於SqlSugar的開發框架的項目程式碼生成,包括框架基礎的程式碼生成,以及Winform介面程式碼生成兩個部分,框架項目及Winform介面效果如上圖所示。

 

2、整合程式碼生成工具Database2Sharp進行SqlSugar框架程式碼生成

前面隨筆介紹過基於SqlSugar核心Core項目的組成。

基礎核心數據模組SugarProjectCore,主要就是開發業務所需的數據處理和業務邏輯的項目,為了方便,我們區分Interface、Modal、Service三個目錄來放置不同的內容,其中Modal是SqlSugar的映射實體,Interface是定義訪問介面,Service是提供具體的數據操作實現。

對於Modal層的類程式碼生成,常規的普通表(非中間表),我們根據項目所需要,生成如下程式碼。目的是利用它定義好對應的主鍵Id,並通過介面約束實體類。

    /// <summary>
    /// 客戶資訊
    /// 繼承自Entity,擁有Id主鍵屬性
    /// </summary>
    [SugarTable("T_Customer")]
    public class CustomerInfo : Entity<string>

而對於中間表,我們不要它的繼承繼承關係。

    /// <summary>
    /// 用戶角色關聯
    /// </summary>
    [SugarTable("T_ACL_User_Role")]
    public class User_RoleInfo
    {
    }

只需要簡單的標註好SugarTable屬性,讓他可以和其他業務表進行關聯查詢即可。

        /// <summary>
        /// 根據用戶ID獲取對應的角色列表
        /// </summary>
        /// <param name="userID">用戶ID</param>
        /// <returns></returns>
        private async Task<List<RoleInfo>> GetByUser(int userID)
        {
            var query = this.Client.Queryable<RoleInfo, User_RoleInfo>(
            (t, m) => t.Id == m.Role_ID && m.User_ID == userID)
            .Select(t => t); //聯合條件獲取對象

            query = query.OrderBy(t => t.CreateTime);//排序
            var list = await query.ToListAsync();//獲取列表
            return list;
        }

對於介面層的類,我們只需要按固定的繼承關係處理好,以及類的名稱變化即可。

    /// <summary>
    /// 系統用戶資訊,應用層服務介面定義
    /// </summary>
    public interface IUserService : IMyCrudService<UserInfo, int, UserPagedDto>, ITransientDependency
    {
    }

其中 IMyCrudService 是我們定義的基類介面,保存常規的增刪改查等的處理基類,通過傳入泛型進行約束介面參數類型和返回值。

基類介面儘可能滿足實際項目介面所需,這樣可以減少子類的程式碼編寫,以及獲得統一調用基類函數的便利。

 

對於中間表,我們除了生成實體類外,不需要生成其他介面和介面實現層,因為我們不單獨調用它們。

對於具體業務對象對應的介面實現,我們除了確定它的繼承關係外,我們還會重寫它們的一些基類函數,從而實現更加精準的處理。

 介面實現類的定義如下所示。

    /// <summary>
    /// 應用層服務介面實現
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {

    }

一般情況下,我們至少需要在子類重寫 CreateFilteredQueryAsync 和 ApplyDefaultSorting 兩個函數,前者是條件的查詢處理,後者是默認的排序處理操作。

        /// <summary>
        /// 自定義條件處理
        /// </summary>
        /// <param name="input">查詢條件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
        {
            var query = 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.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
                .WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
                ;

            return query;
        }

        /// <summary>
        /// 自定義排序處理
        /// </summary>
        /// <param name="query">可查詢LINQ</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> ApplyDefaultSorting(ISugarQueryable<CustomerInfo> query)
        {
            return query.OrderBy(t => t.CreateTime, OrderByType.Desc);

            //如果先按第一個欄位排序,然後再按第二欄位排序,示例程式碼
            //return base.ApplySorting(query, input).OrderBy(s=>s.Customer_ID).OrderBy(s => s.Seq);
        }

根據這些規則,編寫我們所需的模板程式碼,讓我們選擇的資料庫表名稱、注釋,以及表欄位的名稱、類型、注釋,外鍵主鍵關係等資訊為我們模板所用。

如下所示程式碼是NVelocity模板程式碼,用於生成上面的條件查詢處理的,可以稍作了解。

        /// <summary>
        /// 自定義條件處理
        /// </summary>
        /// <param name="input">查詢條件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<${ClassName}Info> CreateFilteredQueryAsync(${ClassName}PagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);
             query = query
#if(${PrimaryKeyNetType}=="string")
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t=>t.Id != input.ExcludeId) //不包含排除ID
#else
                .WhereIF(input.ExcludeId.HasValue, t=>t.Id != input.ExcludeId) //不包含排除ID
#end
#foreach($EntityProperty in $EntityPropertyList) 
#if(${EntityProperty.ColumnInfo.IsForeignKey} || ${EntityProperty.PropertyName}  == "Status" || ${EntityProperty.PropertyName}  == "State" || ${EntityProperty.PropertyName}  == "PID" || ${EntityProperty.PropertyName}  == "Deleted")
                .WhereIF(#if(${EntityProperty.ColumnInfo.IsNumeric})input.${EntityProperty.PropertyName}.HasValue#else!input.${EntityProperty.PropertyName}.IsNullOrWhiteSpace()#end, s => s.${EntityProperty.PropertyName} == input.${EntityProperty.PropertyName})
#elseif(${EntityProperty.ColumnInfo.IsDateTime} || ${EntityProperty.ColumnInfo.IsNumeric})
                //${EntityProperty.Description}區間查詢
                .WhereIF(input.${EntityProperty.PropertyName}Start.HasValue, s => s.${EntityProperty.PropertyName} >= input.${EntityProperty.PropertyName}Start.Value)
                .WhereIF(input.${EntityProperty.PropertyName}End.HasValue, s => s.${EntityProperty.PropertyName} <= input.${EntityProperty.PropertyName}End.Value)
#elseif(${EntityProperty.ColumnInfo.NetType.Alias.ToLower()} != "byte[]" && ${EntityProperty.ColumnInfo.Name.Name.ToString()} != "AttachGUID")
                .WhereIF(#if(${EntityProperty.NetType.EndsWith("?")})input.${EntityProperty.PropertyName}.HasValue, t => t.${EntityProperty.PropertyName} == input.${EntityProperty.PropertyName}#else!input.${EntityProperty.PropertyName}.IsNullOrWhiteSpace(), t => t.${EntityProperty.PropertyName}.Contains(input.${EntityProperty.PropertyName})#end) //如需要精確匹配則用Equals
#end ##endif
#end

#if(${HasCreationTime}) 
                //創建日期區間查詢
                .WhereIF(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
                .WhereIF(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value)
#else
                //創建日期區間查詢(參考)
                //.WhereIF(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
                //.WhereIF(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value)
#end;
            return query;
        }

 

當我們完成所需的模板程式碼開發後,就在程式碼生成工具主體介面中整合相關的生成功能菜單,介面效果如下所示。

 通過菜單選擇【SqlSugar框架程式碼生成】,進一步選擇資料庫中的表進行生成,一步步處理即可,最後列出所選資料庫表,並確認生成操作,即可生成SqlSugar框架核心項目的程式碼,如下圖所示。

選擇表進行生成後,生成的實體模型類如下所示,包括生成了中間表的實體類。

 

 而介面實現則是根據具體的業務對象規則進行生成。

 

 

3、SqlSugar項目中Winform介面的生成 

Winform介面包括普通列表/編輯介面處理,以及主從表介面處理兩個部分,如下圖所示。

生成的簡單業務表介面,包括分頁列表展示介面,在列表介面中整合查看、編輯、新增、刪除、導入、導出、查詢/高級查詢等功能,整合的編輯介面也是依據資料庫表的資訊進行生成的。

列表介面和編輯介面效果如下所示。

而主從表介面生成的效果如下所示。

我們看看生成的Winform列表介面程式碼,如下所示。

另外我們把一些常用的處理邏輯放在函數中統一處理,如AddData、EditData、DeleteData、BindData、GetData、ImportData、ExportData等等,如下所示。

 

 在獲取數據的時候,我們根據用戶的條件,構建一個分頁查詢對象傳遞,調用介面獲得數據後,進行分頁控制項的綁定處理即可。

        /// <summary>
        /// 獲取數據
        /// </summary>
        /// <returns></returns>
        private async Task<IPagedResult<CustomerInfo>> GetData()
        {
            CustomerPagedDto pagerDto = null;
            if (advanceCondition != null)
            {
                //如果有高級查詢,那麼根據輸入資訊構建查詢條件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo);
                pagerDto = dlg.GetPagedResult(pagerDto);
            }
            else
            {
                //構建分頁的條件和查詢條件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo)
                {
                    //添加所需條件
                    Name = this.txtName.Text.Trim(),
                };

                //日期和數值範圍定義
                //年齡,需在CustomerPagedDto中添加 int? 類型欄位AgeStart和AgeEnd
                var Age = new ValueRange<int?>(this.txtAge1.Text, this.txtAge2.Text); //數值類型
                pagerDto.AgeStart = Age.Start;
                pagerDto.AgeEnd = Age.End;

                //創建時間,需在CustomerPagedDto中添加 DateTime? 類型欄位CreationTimeStart和CreationTimeEnd
                var CreationTime = new TimeRange(this.txtCreationTime1.Text, this.txtCreationTime2.Text); //日期類型
                pagerDto.CreateTimeStart = CreationTime.Start;
                pagerDto.CreateTimeEnd = CreationTime.End;
            }

            var result = await BLLFactory<CustomerService>.Instance.GetListAsync(pagerDto);
            return result;
        }

如果是高級查詢,我們則是根據傳入分頁查詢對象的屬性在高級查詢對話框中進行賦值,然後獲得對象後進行查詢獲得記錄的。

 

 在程式碼生成工具中,我們根據實際項目的程式碼,定義好對應的模板文件,如下所示。

 最後在生成程式碼的時候,整合這些NVelocity的模板文件,根據表對象的資訊,生成對應的文件供我們開發使用即可。

            #region Model 實體部分

            string entityTemplateFile = ProjectPath + "Templates/Entity.cs.vm";
            var entityAdapter = new SugarEntityAdapter(databaseInfo, selectedTableNames, entityTemplateFile);
            entityAdapter.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Modal";
            entityAdapter.Execute();

            #endregion

            #region Interface部分和Application部分

            var appInterface = new SugarServiceInterfaceAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/IService.cs.vm", databaseTypeName);
            appInterface.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Interface";
            appInterface.Execute();

            var appService = new SugarServiceAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/Service.cs.vm", databaseTypeName);
            appService.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Service";
            appService.Execute();

            #endregion

            #region Web API Controller 部分

            var controller = new SugarControllerAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/Controller.cs.vm", databaseTypeName);
            controller.DirectoryOfOutput = mainSetting.RootNameSpace + "/Controller";
            controller.Execute();

            #endregion            

 

系列文章:

基於SqlSugar的開發框架的循序漸進介紹(1)–框架基礎類的設計和使用

基於SqlSugar的開發框架循序漸進介紹(2)– 基於中間表的查詢處理

基於SqlSugar的開發框架循序漸進介紹(3)– 實現程式碼生成工具Database2Sharp的整合開發

 

Tags: