zoukankan      html  css  js  c++  java
  • 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分钟呀!

    本文版权归作者 【陌城&心灵捕手】博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    第二十九天- socketserver模块 ftp上传
    第二十八天- tcp下的粘包和解决方案
    记录每次更新到仓库
    Python在自动化运维时经常会用到的方法
    运维新手们,别再问需不需要学PYTHON了!!!
    nagios监控系统搭建!!
    Nagios:企业级系统监控方案
    打造自己的Cacti模板
    Cacti插件
    Cacti脚本及模板
  • 原文地址:https://www.cnblogs.com/StrangeCity/p/4352753.html
Copyright © 2011-2022 走看看