30分鍾教您打造自己的代碼生成器


     

      哇咔咔,離上次寫文又有幾個月了。不過上次寫的《這些開源項目,你都知道嗎?(持續更新中...)[原創]》受到廣大讀者的贊賞和推薦,也使得自己更有動力去寫技術文章。好了,廢話不多說,直接切入正題。

      前方高能預警,由於此篇博文適合初學者入門或者各種大牛老男孩懷念那些年我們一起寫過的代碼生成器,所以請各路時間寶貴的大神慎重查看此篇博文,誤入者后果自負!!! 

一、前言背景(3分鍾)

      博主清晰的記得,大三的時候,老師會布置各種課外作業,不過大多是基於數據庫增刪改查的XX管理系統。此類系統,想必大家伙都懂,自然是無法避免的要編寫很多重復性的簡單代碼。基於XX表的DAL、BLL,其實都不必用三層架構,直接兩層多清爽。不過學生時代很多時候都是為了學習而生搬硬套。不像現在各種ORM,寫個錘子的DAL。至於大名鼎鼎的T4,用起來似乎智能提示不怎么友好,另外就是針對特性化需求實現起來也麻煩。還有就是各種各樣成熟的代碼生成器(比如動軟),也始終難以滿足團隊內部的代碼生成規則。再加上那時的博主本着一顆造輪子的心,開始腦海中浮現,是否可以做一個工具?主要用以實現對應數據庫中表的基礎代碼的自動生成,包括生成屬性、增刪改查、實體類等基礎代碼片斷。使開發人員可以節省大量機械式編寫代碼的時間和重復勞動,而將精力集中於核心業務邏輯的開發。從而使得開發人員能夠快速開發項目,縮短開發周期,減少開發成本,大大提高了項目的研發效率,使得開發人員在同樣的時間創造出更大的價值。

 

二、技術儲備(2分鍾)

1、既然要生成數據庫表所對應的代碼,那么想必得從數據庫獲取相關表和字段的基礎信息吧?

2、有了相關表和字段的基礎信息了,怎么樣按照特性化需求和規則生成對應的代碼呢?

3、生成的代碼如何展現?直接打印到界面還是生成代碼文件呢?

 

三、開始構建(20分鍾)

此例子,博主將使用SQL Server 2008 R2 做數據庫,使用Winform做工具的UI展示。

1、執行以下sql,即可從SQL server 數據庫得到相關表和字段的基礎信息(SQL Server 2008 R2 親測有效)

1 SELECT *
2 FROM master..sysdatabases
獲取一個連接上的所有數據庫信息
 1 select  
 2     [表名]=c.Name, 
 3     [表說明]=isnull(f.[value],''),  
 4     [列序號]=a.Column_id,  
 5     [列名]=a.Name,  
 6     [列說明]=isnull(e.[value],''),  
 7     [數據庫類型]=b.Name,    
 8     [類型]= case when b.Name = 'image' then 'byte[]'
 9                  when b.Name in('image','uniqueidentifier','ntext','varchar','ntext','nchar','nvarchar','text','char') then 'string'
10                  when b.Name in('tinyint','smallint','int','bigint') then 'int'
11                  when b.Name in('datetime','smalldatetime') then 'DateTime'
12                  when b.Name in('float','decimal','numeric','money','real','smallmoney') then 'decimal'
13                  when b.Name ='bit' then 'bool' else b.name end ,
14     [標識]= case when is_identity=1 then '' else '' end,  
15     [主鍵]= case when exists(select 1 from sys.objects x join sys.indexes y on x.Type=N'PK' and x.Name=y.Name  
16                         join sysindexkeys z on z.ID=a.Object_id and z.indid=y.index_id and z.Colid=a.Column_id)  
17                     then '' else '' end,      
18     [字節數]=case when a.[max_length]=-1 and b.Name!='xml' then 'max/2G'  
19                   when b.Name='xml' then '2^31-1字節/2G' 
20                   else rtrim(a.[max_length]) end,  
21     [長度]=case when ColumnProperty(a.object_id,a.Name,'Precision')=-1 then '2^31-1' 
22                 else rtrim(ColumnProperty(a.object_id,a.Name,'Precision')) end,  
23     [小數位]=isnull(ColumnProperty(a.object_id,a.Name,'Scale'),0),  
24     [是否為空]=case when a.is_nullable=1 then '' else '' end,      
25     [默認值]=isnull(d.text,'')      
26 from  
27     sys.columns a  
28 left join 
29     sys.types b on a.user_type_id=b.user_type_id  
30 inner join 
31     sys.objects c on a.object_id=c.object_id and c.Type='U' 
32 left join 
33     syscomments d on a.default_object_id=d.ID  
34 left join 
35     sys.extended_properties e on e.major_id=c.object_id and e.minor_id=a.Column_id and e.class=1   
36 left join 
37     sys.extended_properties f on f.major_id=c.object_id and f.minor_id=0 and f.class=1 
38 
39 獲取數據庫所有表字段信息
獲取一個數據庫上的所有表字段信息

2、構建自定義模板,然后替換模板中的標識符

說到自定義模板,無非就是一堆字符串,那這一堆字符串究竟是用XML存儲還是TXT文本存儲還是其他方式呢?好吧,咱不糾結到底采用哪種存儲介質了。基於本文的“30分鍾”,博主決定短平快,直接硬編碼寫死吧!用字符串對象存儲起來。 

 1 string modelTmp = @"
 2 using System;
 3 
 4 namespace #ModelNamespace#
 5 {
 6     /// <summary>
 7     /// #ModelClassDescription#
 8     /// Create By Tool #CreateDateTime#
 9     /// </summary>
10     public class #ModelClassName#
11     {
12 #PropertyInfo#
13     }
14 }";
15 
16 實體類模板
實體類模板
1 string modelPropertyTmp = @"
2          /// <summary>
3          /// #PropertyDescription#
4          /// </summary>
5          public  #PropertyType# #PropertyName# { get; set; }";
實體類屬性模板
 1 propertyInfo += modelPropertyTmp.Replace(" #PropertyDescription#", propertyDescription)
 2                                                         .Replace(" #PropertyType#", propertyType)
 3                                                         .Replace("#PropertyName#", propertyName);
 4 
 5 
 6 modelTmp = modelTmp.Replace("#ModelClassDescription#", this.treeView1.SelectedNode.Tag.ToString())
 7                                        .Replace("#CreateDateTime#", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
 8                                        .Replace("#ModelNamespace#", modelNamespace)
 9                                        .Replace("#ModelClassName#", modelClassName)
10                                        .Replace("#PropertyInfo#", propertyInfo);
11 
12 標識符替換
替換模板中的標識符

3、采用文本控件將生成的代碼打印出來

由於本文旨在讓各位讀者了解代碼生成的原理,故博主花了10多分鍾寫了一個簡單的代碼生成器,代碼自然有些粗糙,請各位見諒!

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data;
  4 using System.Data.SqlClient;
  5 using System.Linq;
  6 using System.Windows.Forms;
  7 
  8 namespace WindowsTest
  9 {
 10     public partial class CodeCreate : Form
 11     {
 12         private static string S_conStr = "Data Source=127.0.0.1;Initial Catalog=master;Integrated Security=False;user=sa;password=******;";
 13 
 14         public CodeCreate()
 15         {
 16             InitializeComponent();
 17         }
 18 
 19         public static DataTable ExcuteQuery(string connectionString, string cmdText, List<SqlParameter> pars = null)
 20         {
 21             using (SqlConnection conn = new SqlConnection(connectionString))
 22             {
 23                 using (SqlCommand cmd = new SqlCommand(cmdText, conn))
 24                 {
 25                     if (pars != null && pars.Count > 0) cmd.Parameters.AddRange(pars.ToArray());
 26                     using (SqlDataAdapter adp = new SqlDataAdapter(cmd))
 27                     {
 28                         DataTable dt = new DataTable();
 29                         adp.Fill(dt);
 30                         return dt;
 31                     }
 32                 }
 33             }
 34         }
 35 
 36         private void CodeCreate_Load(object sender, EventArgs e)
 37         {
 38             #region 獲取所有數據庫的信息
 39             string sql = @" SELECT *
 40                             FROM master..sysdatabases
 41                             where dbid>6
 42                             ORDER BY dbid";
 43             #endregion
 44 
 45             DataTable dt = ExcuteQuery(S_conStr, sql);
 46             this.treeView1.Nodes.Add("192.168.30.69");
 47             foreach (DataRow dr in dt.Rows)
 48             {
 49                 this.treeView1.Nodes[0].Nodes.Add(dr["name"].ToString());
 50             }
 51 
 52             this.treeView1.ExpandAll();
 53         }
 54 
 55         private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
 56         {
 57             this.tabControl1.SelectedIndex = 0;
 58 
 59             if (e.Node.Level == 0) return;
 60 
 61             #region 獲取數據庫所有表字段信息的sql
 62             string sql = @"
 63 select  
 64     [表名]=c.Name, 
 65     [表說明]=isnull(f.[value],''),  
 66     [列序號]=a.Column_id,  
 67     [列名]=a.Name,  
 68     [列說明]=isnull(e.[value],''),  
 69     [數據庫類型]=b.Name,    
 70     [類型]= case when b.Name = 'image' then 'byte[]'
 71                  when b.Name in('image','uniqueidentifier','ntext','varchar','ntext','nchar','nvarchar','text','char') then 'string'
 72                  when b.Name in('tinyint','smallint','int','bigint') then 'int'
 73                  when b.Name in('datetime','smalldatetime') then 'DateTime'
 74                  when b.Name in('float','decimal','numeric','money','real','smallmoney') then 'decimal'
 75                  when b.Name ='bit' then 'bool' else b.name end ,
 76     [標識]= case when is_identity=1 then '是' else '' end,  
 77     [主鍵]= case when exists(select 1 from sys.objects x join sys.indexes y on x.Type=N'PK' and x.Name=y.Name  
 78                         join sysindexkeys z on z.ID=a.Object_id and z.indid=y.index_id and z.Colid=a.Column_id)  
 79                     then '是' else '' end,      
 80     [字節數]=case when a.[max_length]=-1 and b.Name!='xml' then 'max/2G'  
 81                   when b.Name='xml' then '2^31-1字節/2G' 
 82                   else rtrim(a.[max_length]) end,  
 83     [長度]=case when ColumnProperty(a.object_id,a.Name,'Precision')=-1 then '2^31-1' 
 84                 else rtrim(ColumnProperty(a.object_id,a.Name,'Precision')) end,  
 85     [小數位]=isnull(ColumnProperty(a.object_id,a.Name,'Scale'),0),  
 86     [是否為空]=case when a.is_nullable=1 then '是' else '' end,      
 87     [默認值]=isnull(d.text,'')      
 88 from  
 89     sys.columns a  
 90 left join 
 91     sys.types b on a.user_type_id=b.user_type_id  
 92 inner join 
 93     sys.objects c on a.object_id=c.object_id and c.Type='U' 
 94 left join 
 95     syscomments d on a.default_object_id=d.ID  
 96 left join 
 97     sys.extended_properties e on e.major_id=c.object_id and e.minor_id=a.Column_id and e.class=1   
 98 left join 
 99     sys.extended_properties f on f.major_id=c.object_id and f.minor_id=0 and f.class=1 ";
100             #endregion
101 
102             if (e.Node.Level == 1)
103             {
104                 DataTable dt = ExcuteQuery(S_conStr.Replace("master", e.Node.Text), sql);
105                 this.dataGridView1.DataSource = dt;
106 
107                 if (dt.Rows.Count > 0)
108                 {
109                     e.Node.Nodes.Clear();
110                     DataRow[] aryDr = new DataRow[dt.Rows.Count];
111                     dt.Rows.CopyTo(aryDr, 0);
112                     List<string> listTableName = aryDr.Select(a => a["表名"].ToString()).Distinct().ToList();
113                     listTableName.ForEach(a =>
114                     {
115                         e.Node.Nodes.Add(a, a);
116                     });
117                     e.Node.ExpandAll();
118                 }
119             }
120 
121             if (e.Node.Level == 2)
122             {
123                 sql += "where c.Name=@TableName ";
124                 List<SqlParameter> listSqlParameter = new List<SqlParameter>()
125                 {
126                     new SqlParameter("@TableName",e.Node.Text),
127                 };
128                 DataTable dt = ExcuteQuery(S_conStr.Replace("master", e.Node.Parent.Text), sql, listSqlParameter);
129                 if (dt.Columns.Count > 0)
130                 {
131                     if (dt.Rows.Count > 0)
132                     {
133                         e.Node.Tag = dt.Rows[0]["表說明"].ToString();
134                     }
135                     dt.Columns.Remove("表名");
136                     dt.Columns.Remove("表說明");
137                 }
138                 this.dataGridView1.DataSource = dt;
139             }
140         }
141 
142         private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
143         {
144             if (this.tabControl1.SelectedTab.Text == "Model")
145             {
146                 if (this.treeView1.SelectedNode.Level == 2 && this.dataGridView1.Rows.Count > 0)
147                 {
148                     string modelTmp = @"
149 using System;
150 
151 namespace #ModelNamespace#
152 {
153     /// <summary>
154     /// #ModelClassDescription#
155     /// Create By Tool #CreateDateTime#
156     /// </summary>
157     public class #ModelClassName#
158     {
159 #PropertyInfo#
160     }
161 }";
162 
163                     string modelNamespace = "Model";
164                     string modelClassName = this.treeView1.SelectedNode.Text;
165 
166                     string propertyInfo = string.Empty;
167                     foreach (DataGridViewRow dgvr in this.dataGridView1.Rows)
168                     {
169                         string modelPropertyTmp = @"
170         /// <summary>
171         /// #PropertyDescription#
172         /// </summary>
173         public  #PropertyType# #PropertyName# { get; set; }";
174 
175                         string propertyDescription = dgvr.Cells["列說明"].Value.ToString().Trim();
176                         string propertyName = dgvr.Cells["列名"].Value.ToString().Trim();
177                         string propertyType = dgvr.Cells["類型"].Value.ToString().Trim();
178 
179                         propertyInfo += modelPropertyTmp.Replace(" #PropertyDescription#", propertyDescription)
180                                                         .Replace(" #PropertyType#", propertyType)
181                                                         .Replace("#PropertyName#", propertyName);
182                         propertyInfo += Environment.NewLine;
183                     }
184 
185                     modelTmp = modelTmp.Replace("#ModelClassDescription#", this.treeView1.SelectedNode.Tag.ToString())
186                                        .Replace("#CreateDateTime#", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
187                                        .Replace("#ModelNamespace#", modelNamespace)
188                                        .Replace("#ModelClassName#", modelClassName)
189                                        .Replace("#PropertyInfo#", propertyInfo);
190 
191                     this.richTextBox1.Text = modelTmp;
192                     Clipboard.SetDataObject(this.richTextBox1.Text);
193                 }
194             }
195         }
196     }
197 }
198 
199 主要代碼
示例代碼

 

四、結語思考(5分鍾)

怎么樣,相信各位讀者對代碼生成有了基本認識了吧!我想普通的代碼生成無非就是替換,當然你要是想做一個好一點的代碼生成工具,自然是要考慮可擴展性,可維護性,健壯性等。比如可以在左側的樹形菜單那里加上下文菜單,新建連接,斷開連接,刷新等擴展功能。在代碼打印的區域可以增加另存為,復制等功能。還可以增加相關配置界面,例如命名空間,數據庫連接,代碼文件默認保存路徑等等。自己做的代碼生成器可以由自己一直不斷的維護,針對特性化需求可以馬上實現。好了,本文主要是簡潔明了的讓各位讀者對代碼生成原理有一個基本認識,畢竟就30分鍾呀!

 


免責聲明!

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



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