哇咔咔,離上次寫文又有幾個月了。不過上次寫的《這些開源項目,你都知道嗎?(持續更新中...)[原創]》受到廣大讀者的贊賞和推薦,也使得自己更有動力去寫技術文章。好了,廢話不多說,直接切入正題。
前方高能預警,由於此篇博文適合初學者入門或者各種大牛老男孩懷念那些年我們一起寫過的代碼生成器,所以請各路時間寶貴的大神慎重查看此篇博文,誤入者后果自負!!!
一、前言背景(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分鍾呀!