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


上篇給大家從零開始搭建了一個我們的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 Physical 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跟大伙一起討論。共同進步!共勉!


免責聲明!

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



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