.NET Core實戰項目之CMS 第十一章 開發篇-資料庫生成及實體代碼生成器開發

来源:https://www.cnblogs.com/yilezhu/archive/2018/12/13/10112406.html
-Advertisement-
Play Games

上篇給大家從零開始搭建了一個我們的ASP.NET Core CMS系統的開發框架,具體為什麼那樣設計我也已經在第十篇文章中進行了說明。不過文章發佈後很多人都說了這樣的分層不是很合理,什麼資料庫實體應該跟倉儲放在一起形成領域對象,什麼ViewModel應該放在應用層結構倉儲層與UI層。其實我想說的是, ...


上篇給大家從零開始搭建了一個我們的ASP.NET Core CMS系統的開發框架,具體為什麼那樣設計我也已經在第十篇文章中進行了說明。不過文章發佈後很多人都說了這樣的分層不是很合理,什麼資料庫實體應該跟倉儲放在一起形成領域對象,什麼ViewModel應該放在應用層結構倉儲層與UI層。其實我想說的是,這樣都沒問題,看你自己的理解了!我上篇文章已經說了,如果你願意,完全可以把所有的層融合在一起,隨意合併分離這個依你個人喜好。
我也是本著簡單原則以及合適原則的思想來進行那樣的分層結構,覺得這樣層次更分明些。還有雖然現在DDD的思想很流行,但是實現起來確很複雜,小項目就別那樣折騰了。如果你有不同的意見,歡迎加群討論。什麼?你問我群號?自己找去,我才不會告訴你!

本文已收錄至《.NET Core實戰項目之CMS 第一章 入門篇-開篇及總體規劃
作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/10112406.html

寫在前面

今天我們就進入.NET Core實戰項目之CMS的開發篇了,在開始之前呢我們首先需要把我們前面設計的邏輯模型轉換成對應的物理模型,再根據我們物理模型生成相應的資料庫腳本,接著我們就新建資料庫,然後執行下我們生成的腳本即可。當然這麼多表如果一個一個的寫對應的資料庫實體模型,一個一個的寫倉儲層代碼以及服務層代碼,感覺就是在搬磚啊,有木有,所以當然得自己實現個代碼生成器來自動生成這些代碼了!下麵我們一步步來先生成下資料庫然後再打造一個實體模型的代碼生成器吧!

資料庫生成

生成物理模型

  1. 首先用pdm打開我們設計的邏輯模型文件,尾碼名是ldm的文件,如下圖所示:

    1544535967072

  2. 依次點擊“Tools”-》"Generate Logical Data Model",如下圖所示。或者直接使用快捷鍵Ctrl+Shift+P 打開物理模型生成選項對話框。

    1544536138034

  3. 如下圖所示選擇號對應的資料庫後,自定義物理模型的名稱代碼後點擊確定即可生成物理模型。這裡資料庫類型有很多選擇如:mysql,sqlserver,oracle等等,我們選擇sqlserver2008,你可以隨意從下拉框選擇一個資料庫進行生成(當然要跟你的資料庫對應)!

    1544536437133

  4. 註意這裡生成的物理模型預設是不會生成註釋的如下圖所示:

    1544537349509

    怎麼辦呢?如何才能講Name 列的內容拷貝到Comment這個裡面呢?因為這個Commment裡面的內容才會真正的轉換到資料庫欄位的註釋。

    這時候你需要Tools->Execute Commands->Edit/Run Scripts (或者快捷鍵Ctrl+Shift+X)打開腳本執行的視窗,然後把下麵的代碼拷貝進行 run一下即可。

    '代碼一:將Name中的字元COPY至Comment中
    
    
    Option   Explicit 
    ValidationMode   =   True 
    InteractiveMode   =   im_Batch
    
    Dim   mdl   '   the   current   model
    
    '   get   the   current   active   model 
    Set   mdl   =   ActiveModel 
    If   (mdl   Is   Nothing)   Then 
          MsgBox   "There   is   no   current   Model " 
    ElseIf   Not   mdl.IsKindOf(PdPDM.cls_Model)   Then 
          MsgBox   "The   current   model   is   not   an   Physical   Data   model. " 
    Else 
          ProcessFolder   mdl 
    End   If
    
    '   This   routine   copy   name   into   comment   for   each   table,   each   column   and   each   view 
    '   of   the   current   folder 
    Private   sub   ProcessFolder(folder) 
          Dim   Tab   'running     table 
          for   each   Tab   in   folder.tables 
                if   not   tab.isShortcut   then 
                      tab.comment   =   tab.name 
                      Dim   col   '   running   column 
                      for   each   col   in   tab.columns 
                            col.comment=   col.name 
                      next 
                end   if 
          next
    
          Dim   view   'running   view 
          for   each   view   in   folder.Views 
                if   not   view.isShortcut   then 
                      view.comment   =   view.name 
                end   if 
          next
    
          '   go   into   the   sub-packages 
          Dim   f   '   running   folder 
          For   Each   f   In   folder.Packages 
                if   not   f.IsShortcut   then 
                      ProcessFolder   f 
                end   if 
          Next 
    end   sub
    

    1544537692272

    這裡腳本執行的很快,你也可以把腳本保存起來下次再用,執行後的效果如下所示:

    1544537771197

    我們的Comment這一行的內容已經跟Name一樣了!

資料庫腳本生成

  1. 首先打開我們生成的物理模型,擴展名為pdm的文件,如下圖所示,乍一看跟物理模型差不多,實際上還是有區別的!

    1544538222229

  2. 然後依次如下圖所示選擇“Database”->"Generate Database" 或者快捷鍵Ctrl+G打開資料庫生成選項對話框

    1544538320805

  3. 如下圖所示設置一下生成的資料庫腳本的路徑以及腳本名稱即可生成資料庫腳本文件,如下圖所示:

    1544539197457

  4. 到我們上面設置的文件夾里即可查看到我們生成的資料庫腳本,如下圖所示:

    1544539359326

資料庫生成

  1. 打開我們的資料庫,並新建一個名為CzarCms的新的資料庫,如下圖所示:

    1544539477339

  2. 選擇我們新建的資料庫,然後按照如下圖所示的方式打開我們剛纔生成的資料庫腳本

    1544539621124

  3. 如下圖所示確認一下目前選擇的是你剛新建的資料庫,然後點擊執行,執行下腳本

    1544539734388

  4. 不出以外的話會出現如下圖所示的“命令已成功完成”的消息,這表示腳本執行成功了,然後刷新下我們剛纔的資料庫,可以看到我們的表已經生成成功了!

    1544539898998

  5. 這裡你可以檢查下,看看生成的資料庫表有沒有問題,如果有問題的話,重新走一遍流程生成腳本然後執行下就行了,不過需要註意的是,如果你資料庫中有數據就要當心了,重新生成的腳本會drop掉你的表重新創建,所以如果是個別欄位出問題的話就邏輯模型以及物理模型修改後,手動在資料庫中修改即可!

  6. 這裡我給每個表的主鍵設計了自增,給isdelete等等設置了預設的0,以及addtime設置了getdate()等等。

實體模型生成器編寫

好了,上面我已經帶著你一步一步的演示了資料庫的創建過程,下麵我就帶著你實現一個簡單的POCO實體對象的代碼生成器吧!什麼?市面上不是有很多代碼生成器嗎?靠,我就是要帶著你自己實現一個,咋滴?是用別人的爽,還是用自己實現的爽呢?自己琢磨吧!

思考

大家先腦補一下,如果是你想根據資料庫實現一個代碼生成器你的思路是怎樣的呢?是不是首先得獲取下資料庫裡面的所有的表,然後獲取這些表對應的列以及列的類型,是否為空等等信息。然後再建一個模板,迴圈這些表的信息來根據模板創建對應的文件呢。
至於模板文件可能你會想到T4或者CodeSmish模板等等,可這些都太複雜了,複雜的語法以及靈活性的問題我這裡選擇另一個文本文件的形式來進行代碼的生成。

這個代碼生成器的靈感以及部分代碼來自於Zxw.Framework.NetCore,這個框架的github地址是:https://github.com/VictorTzeng/Zxw.Framework.NetCore/tree/master/Zxw.Framework.NetCore 有興趣的小伙伴可以看下。

下麵就讓我們簡單實現下我們自己的實體模型代碼生成器吧.

實體代碼生成器

  1. 首先我們創建一個Option對象來接收我們所需要的參數,比如說:資料庫類型,資料庫連接字元串,作者,實體模型的命名空間等等,如下所示:

     /// <summary>
        /// yilezhu
        /// 2018.12.12
        /// 代碼生成選項
        /// </summary>
        public class CodeGenerateOption
        {
            /// <summary>
            /// 資料庫連接字元串
            /// </summary>
            public string ConnectionString { get; set; }
            /// <summary>
            /// 資料庫類型
            /// </summary>
            public string DbType  { get; set; }
            /// <summary>
            /// 作者
            /// </summary>
            public string Author { get; set; }
    
            /// <summary>
            /// 代碼生成時間
            /// </summary>
            public string GeneratorTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    
            /// <summary>
            /// 輸出路徑
            /// </summary>
            public string OutputPath { get; set; }
    
            /// <summary>
            /// 實體命名空間
            /// </summary>
            public string ModelsNamespace { get; set; }
        }
  2. 從資料庫裡面獲取所有表的腳本,這裡我只是簡單的實現了下SqlServer的代碼,後續我會對這塊進行提取封裝,並支持MySql,Oracle,PSQL等等:

    //TODO 從資料庫獲取表列表以及生成實體對象
                if (_options.DbType != DatabaseType.SqlServer.ToString())
                    throw new ArgumentNullException("這是我的錯,目前只支持MSSQL資料庫的代碼生成!後續更新MySQL");
                DatabaseType dbType = DatabaseType.SqlServer;
                string strGetAllTables = @"SELECT DISTINCT d.name as TableName, f.value as TableComment
    FROM      sys.syscolumns AS a LEFT OUTER JOIN
                    sys.systypes AS b ON a.xusertype = b.xusertype INNER JOIN
                    sys.sysobjects AS d ON a.id = d.id AND d.xtype = 'U' AND d.name <> 'dtproperties' LEFT OUTER JOIN
                    sys.syscomments AS e ON a.cdefault = e.id LEFT OUTER JOIN
                    sys.extended_properties AS g ON a.id = g.major_id AND a.colid = g.minor_id LEFT OUTER JOIN
                    sys.extended_properties AS f ON d.id = f.major_id AND f.minor_id = 0";
                List<DbTable> tables = null;
                using (var conn = new SqlConnection(_options.ConnectionString))
                {
                    tables = conn.Query<DbTable>(strGetAllTables).ToList();
  3. 遍歷每個表然後獲取每個表對應的列(也是只實現的SqlServer的代碼)

     tables.ForEach(item =>
                    {
                        string strGetTableColumns = @"SELECT   a.name AS ColName, CONVERT(bit, (CASE WHEN COLUMNPROPERTY(a.id, a.name, 'IsIdentity') 
                    = 1 THEN 1 ELSE 0 END)) AS IsIdentity, CONVERT(bit, (CASE WHEN
                        (SELECT   COUNT(*)
                         FROM      sysobjects
                         WHERE   (name IN
                                             (SELECT   name
                                              FROM      sysindexes
                                              WHERE   (id = a.id) AND (indid IN
                                                                  (SELECT   indid
                                                                   FROM      sysindexkeys
                                                                   WHERE   (id = a.id) AND (colid IN
                                                                                       (SELECT   colid
                                                                                        FROM      syscolumns
                                                                                        WHERE   (id = a.id) AND (name = a.name))))))) AND (xtype = 'PK')) 
                    > 0 THEN 1 ELSE 0 END)) AS IsPrimaryKey, b.name AS ColumnType, COLUMNPROPERTY(a.id, a.name, 'PRECISION') 
                    AS ColumnLength, CONVERT(bit, (CASE WHEN a.isnullable = 1 THEN 1 ELSE 0 END)) AS IsNullable, ISNULL(e.text, '') 
                    AS DefaultValue, ISNULL(g.value, ' ') AS Comment
    FROM      sys.syscolumns AS a LEFT OUTER JOIN
                    sys.systypes AS b ON a.xtype = b.xusertype INNER JOIN
                    sys.sysobjects AS d ON a.id = d.id AND d.xtype = 'U' AND d.name <> 'dtproperties' LEFT OUTER JOIN
                    sys.syscomments AS e ON a.cdefault = e.id LEFT OUTER JOIN
                    sys.extended_properties AS g ON a.id = g.major_id AND a.colid = g.minor_id LEFT OUTER JOIN
                    sys.extended_properties AS f ON d.id = f.class AND f.minor_id = 0
    WHERE   (b.name IS NOT NULL) AND (d.name = @TableName)
    ORDER BY a.id, a.colorder";
                        item.Columns = conn.Query<DbTableColumn>(strGetTableColumns, new
                        {
                            TableName = item.TableName
                        }).ToList();
  4. 接下來就是對資料庫獲取的列進行一個轉換,根據資料庫欄位類型轉換成對應的C#類型了

     item.Columns.ForEach(x =>
                        {
                            var csharpType = DbColumnTypeCollection.DbColumnDataTypes.FirstOrDefault(t =>
                                t.DatabaseType == dbType && t.ColumnTypes.Split(',').Any(p =>
                                    p.Trim().Equals(x.ColumnType, StringComparison.OrdinalIgnoreCase)))?.CSharpType;
                            if (string.IsNullOrEmpty(csharpType))
                            {
                                throw new SqlTypeException($"未從字典中找到\"{x.ColumnType}\"對應的C#數據類型,請更新DbColumnTypeCollection類型映射字典。");
                            }
    
                            x.CSharpType = csharpType;
                        });
  5. 既然所有的表以及表對應的列我們都拿到了,那麼我們就可以進行代碼的生成了,當然在生成之前還得創建我們的模板文件:

    // 本代碼由代碼生成器生成請勿隨意改動
    // 生成時間  {GeneratorTime}
    using System;
    
    namespace {ModelsNamespace}
    {
     /// <summary>
     /// {Author}
     /// {GeneratorTime}
     /// {Comment}
     /// </summary>
     public class {ModelName}
     {
         {ModelProperties}
     }
    }
    

    看到沒有,很簡單的POCO對象的樣子,接下來就是生成對應的模板了,具體怎麼生成呢?思考下:是不是首先讀取模板文件到一個string裡面,然後就是簡單的replace了!很簡單吧,具體的代碼我都上傳到了Github上,文章末尾我會給出地址。另外為了大家引用的方便我已經把這個Czar.Cms.Core項目製作成了Nuget包,大家只需要搜索這個包引用下就可以用了!什麼?Nuget包怎麼引用啊?騷年你可以上天了~~~~~

測試實體代碼生成器

  1. Czar.Cms.Test 這個項目添加Nuget包引用,引用後的Nuget如下所示:

    1544663123867

  2. 接下來就是新建一個測試類,然後創建一個依賴註入容器,並把我們需要的Option傳遞進去,如下所示:

     /// <summary>
            /// 構造依賴註入容器,然後傳入參數
            /// </summary>
            /// <returns></returns>
            public IServiceProvider BuildServiceForSqlServer()
            {
                var services = new ServiceCollection();
    
                services.Configure<CodeGenerateOption>(options =>
                {
                    options.ConnectionString = "Data Source=.;Initial Catalog=CzarCms;User ID=sa;Password=1;Persist Security Info=True;Max Pool Size=50;Min Pool Size=0;Connection Lifetime=300;";//這個必須
                    options.DbType = DatabaseType.SqlServer.ToString();//資料庫類型是SqlServer,其他數據類型參照枚舉DatabaseType//這個也必須
                    options.Author = "yilezhu";//作者名稱,隨你,不寫為空
                    options.OutputPath = @"E:\workspace\vs2017\Czar.Cms\src\Czar.Cms.Models";//實體模型輸出路徑,為空則預設為當前程式運行的路徑
                    options.ModelsNamespace = "Czar.Cms.Models";//實體命名空間
                });
                services.AddSingleton<CodeGenerator>();//註入Model代碼生成器
                return services.BuildServiceProvider(); //構建服務提供程式
            }
  3. 接著就是寫我們的測試方法了,代碼如下:

     [Fact]
            public void GeneratorModelForSqlServer()
            {
                var serviceProvider= BuildServiceForSqlServer();
                var codeGenerator = serviceProvider.GetRequiredService<CodeGenerator>();
                codeGenerator.GenerateModelCodesFromDatabase();
                Assert.Equal(0,0);
    
            }
  4. 運行一下我們的Live Unit Testing 然後看一下我們的Czar.Cms.Models下麵已經生成了對應的實體文件,如下圖所示:

    1544663523474

  5. 隨便打開一個看小效果如下:我標註的你猜猜看都是對應的哪個Options

    1544663584296

開源地址

這個系列教程的源碼我會開放在GitHub以及碼雲上,有興趣的朋友可以下載查看!覺得不錯的歡迎Star
GitHub:https://github.com/yilezhu/Czar.Cms
碼雲:https://gitee.com/yilezhu/Czar.Cms
如果你覺得這個系列對您有所幫助的話,歡迎以各種方式進行贊助,當然給個Star支持下也是可以滴!另外一種最簡單粗暴的方式就是下麵這種直接關註我們的公眾號了:

第一時間收到更新推送。

總結

這篇文章我們一步一步的生成了我們的資料庫,然後手把手帶著你實現了我們自己的實體模型代碼生成器來簡化我們的開發過程。接下來我們就開始實現倉儲層應用層的代碼了,同時我們會提取通用部分的代碼來進行模板代碼生成來簡化我們的工作!俗話說的好,不會偷懶的程式員不是一個好爸爸,好丈夫,好兒子,減少代碼的時間多抽點時間陪陪家人吧!如果你有其他想法可以在下方留言,或者加群637326624跟大伙一起討論。共同進步!共勉!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 什麼是遞歸函數? 任何一個方法既可以調用其他方法又可以調用自己,而當這個方法調用自己時,我們就叫它遞歸函數或者遞歸方法! 說白了,就是調用自己。 通常遞歸有兩個特點: 1.遞歸方法一直會調用自己直到某些條件滿足,也就是說一定要有出口; 2.遞歸方法會有一些參數,而它會把這些新的參數值傳遞給自己;(自 ...
  • 任務調度在我們日常開發過程中非常常見,比如:每天晚上0點自動執行某某操作;每周三晚上2點執行某某操作;......當然,我們處理這類問題的方法也有很多,比如:sql的自動任務;windows上創建任務計劃;寫windows服務等等。如果系統比較複雜,相互調用比較頻繁,任務非常多,幾百上千條甚至上萬條 ...
  • 索引: 商業開發實戰總結 一.API 列表 .FirstOrDefaultAsync() .FirstOrDefaultAsync<M>() 如: .FirstOrDefaultAsync<Agent>() , 用於 單表/多表連接 查詢. .FirstOrDefaultAsync<VM>() 如: ...
  • assign assign指令在前面已經使用了多次,它用於為該模板頁面創建或替換一個頂層變數, assign指令的用法有多種,包含創建或替換一個頂層變數,或者創建或替換多個變數等, 它的最簡單的語法如下: <#assign name=value [in namespacehash]>, 這個用法用於 ...
  • ajax post調用WebMethed報錯,返回的信息如下: {“Message”:“處理請求時出錯”,“StackTrace”:“”,“ExceptionType”:“”} 查了一下WebMethed里的方法沒有問題,網上找了好久也沒有什麼解決方案。 後來自己發現可能是返回的json文本太長了, ...
  • 寫在前面 之前有個項目是用asp.net webapi做的,pc和移動端共用api的服務介面,balabala,正好最近在看關於asp.net core方面的資料,各種依賴註入,中間件,處理管道等,而且把webapi和mvc融合到了一起,就想著把之前那個項目移到asp.net core上。 由於之前 ...
  • 1.1 ASP.NET MVC 簡介 ASP.NET是一種構建Web應用程式的框架,它將一般的MVC(Model-View-Controller)模式應用於ASP.NET框架。 1.1.1 MVC模式簡介 MVC將應用程式的用戶界面(User Interface, UI)分為三個主要部分: 模型:一 ...
  • C# -- 使用委托 delegate 執行非同步操作 委托是一種安全地封裝方法的類型,它與 C 和 C++ 中的函數指針類似。 與 C 中的函數指針不同,委托是面向對象的、類型安全的和保險的。 委托的類型由委托的名稱定義。 1. 使用委托非同步執行方法 2. 執行結果: ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...