年前发布了一些文章,是关于.NET数据操作(点击查看)的。刚开始学习编程的时候,总感觉Java中的Hibernate 功能好强大,现在也不可否认它的确强大,特别是它在数据关系处理上,却是那样的让人称叹。
当我那时还不知道.net 中的Linq的时候,一直想自己能够简单的写个ORM映射框架。去年花费了几个月的业务时间终于算是整出来了,一些基本操作都能够实现了,自己号称从数据库操作冗余代码中解脱出来,其实自己很天真,那个框架是多么的不完善。总结了一下,最近超体力透支的准备将其扩展,同时也碰到了一下很头疼的问题,那就是那个实体的生成。
每次都要去受到生成那些代码,是一件非常消耗体力的事情,而李老师的动软代码生成器也不能生成我想要的代码,于是自己查阅了一些资料,写了一个自己需要的代码生成器,在此共享一下。
1.效果图
看东西总是效果图比较直接,首先看看这个代码生成器的效果图。
2.生成代码规则
因为这个实体是服务于自己写的框架,所以自己也规定了一些规则。相关文件可以参考本人 ORM映射解析 相关系列文章。这些文章代码太多,似乎有些难懂。这个框架正在升级版本中,后续继续讲解。
对于数据操作,制定了一系列特性,用于修饰实体类和实体的相关属性,这些特性能够很好的描述对象和数据库表之间的关系 。这个就是数据库和对象之间的桥梁,用特性建立他们之间的关系,用对象管理关系,用对象管理数据库表。
规则一:
[TableAttribute(DBName = "StuDB", Name = "Student", PrimaryKeyName = "Id", IsInternal = false)]
这个特性是用于描述实体类的,映射实体类和数据库表之间的关系。这里的特性定义暂不做讲解,将在后续文章中讲解。 先看看如下表格对此特性的描述
属性 |
作用 |
name
|
数据表名 该字段用于描述数据库对应表的名称,而且该值最好与 数据表名大小写相同。该值有两种类型。 (1)直接自定表的名称 (2)[数据库名].[表名] 如果是(2)情况,则需要分割字符串,将数据库名分割 出来赋值给dBName
|
dBName
|
数据库名 该字段用于描述数据的名称,而且该值最好与 数据库名称大小写相同
|
primaryKeyName
|
主键字段名 该实体必须指定对应数据库表的主键
|
information
|
表实体描述信息
|
isInternal
|
表实体是否国际化 默认为false
|
version
|
表实体版本号 默认为 "V1.0"
|
规则二:
[ColumnAttribute(Name = "Id", IsPrimaryKey = true, AutoIncrement = true, DataType = DataType.Int, CanNull = false) 该特性是用于修饰属性的,该特性描述了数据库字段和实体对象属性之间的关系。
name
|
表字段名称,该属性的值最好与数据表的字段的名称相同。 该字段的值有两种格式: (1) [表名].[字段名] (2) [字段名] 如果该字段的值为(1)情况,则应分割字符串,将字段名 赋值给name属性,表名则赋值给tableName
|
tableName
|
表字段对应表名称 该值是可以为空的,如果name的值的情况满足(1)情况, 可以分割的值赋值给该属性
|
dataType
|
表字段的数据类型 该属性的类型为自定义类型,该字段是一个枚举类型。 该字段描述了25中数据类型
|
length
|
表字段的长度 控制该字段对应的数据库表字段值的最大长度 可以不指定该值
|
canNull
|
表字段是否可以为空 true 可以为空 false 不能为空
|
defaultValue
|
表字段的默认值 默认情况为null
|
isPrimaryKey
|
表字段是否为主键 true 为主键 false 不是外键
|
autoIncrement
|
表字段是否为自动增长列 true 是自动增长列 false 不是自动增长列
|
isUnique |
确定某个字段是否唯一 true 是唯一的 false 不是唯一
|
regularExpress |
表字段的匹配规则 字段匹配规则正则表达式 |
isForeignKey |
表字段是否为外键 true 为外键 false 不是外键
|
foreignTabName |
表字段外键对应的表名称 如果isForeignKey 为true,则需要指定其值
|
information |
表字段的描述信息
|
|
|
3. 生成代码展示
代码
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using CommonData.Entity;
6 using CommonData.Model.Core;
7
8 namespace Entity.School
9 {
10 [TableAttribute(DBName = " StuDB " , Name = " Student " , PrimaryKeyName = " Id " , IsInternal = false )]
11 public class Student:BaseEntity
12 {
13 public Student()
14 {
15 }
16
17 private int id;
18
19 [ColumnAttribute(Name = " Id " , IsPrimaryKey = true , AutoIncrement = true , DataType = DataType.Int, CanNull = false )]
20 public int Id
21 {
22 get { return id; }
23 set { id = value; }
24 }
25
26 private string sname;
27
28 [ColumnAttribute(Name = " Sname " , IsPrimaryKey = false , AutoIncrement = false , DataType = DataType.Nvarchar, CanNull = false )]
29 public string Sname
30 {
31 get { return sname; }
32 set { sname = value; }
33 }
34
35 private string sex;
36
37 [ColumnAttribute(Name = " Sex " , IsPrimaryKey = false , AutoIncrement = false , DataType = DataType.Nvarchar, CanNull = false )]
38 public string Sex
39 {
40 get { return sex; }
41 set { sex = value; }
42 }
43
44 private DateTime birthday;
45
46 [ColumnAttribute(Name = " Birthday " , IsPrimaryKey = false , AutoIncrement = false , DataType = DataType.Datetime, CanNull = false )]
47 public DateTime Birthday
48 {
49 get { return birthday; }
50 set { birthday = value; }
51 }
52
53 private string addr;
54
55 [ColumnAttribute(Name = " Addr " , IsPrimaryKey = false , AutoIncrement = false , DataType = DataType.Nvarchar, CanNull = false )]
56 public string Addr
57 {
58 get { return addr; }
59 set { addr = value; }
60 }
61
62 private string telephone;
63
64 [ColumnAttribute(Name = " Telephone " , IsPrimaryKey = false , AutoIncrement = false , DataType = DataType.Nvarchar, CanNull = false )]
65 public string Telephone
66 {
67 get { return telephone; }
68 set { telephone = value; }
69 }
70 }
71 }
72
其实生成代码很简单,写文件而已。生成一个实体也很简答,查询一下数据库知道数据表有多少个字段以及字段的名称即可。但是这里的实体不仅仅是我们看到的实体那么简单。这个实体描述了数据库表,数据库表字段的属性信息。要生成这样的一个实体并不简单。当大多数人去研究对数据库操作的时候,眼前要实现的功能迷惑了我们的心智,数据操作还有更多不为人知的东西却被我们悄悄的丢弃。下面我们简单的介绍几个数据库系统操作的sql语句,让我们也感受一下代码生成器的神秘之处。
(a).获取数据库服务名
代码
2 {
3 DataTable tableDataSouce = SqlClientFactory.Instance.CreateDataSourceEnumerator().GetDataSources();
4 IList < string > listServerName = new List < string > ();
5 for ( int i = 0 ; i < tableDataSouce.Rows.Count; i ++ )
6 {
7 listServerName.Add(tableDataSouce.Rows[i][ " ServerName " ].ToString() + " \\ " + tableDataSouce.Rows[i][ " InstanceName " ].ToString());
8 }
9 return listServerName;
10 }
我们是使用客户端连接数据库服务器的,在System.Data.SqlClient中为为我们提供了一个实例,来获得服务器的名称和数据库实例。SqlClientFactory.Instance.CreateDataSourceEnumerator().GetDataSources() 这个方法能够返回一个DataTable实例,这个DataTable 中就包含了数据库服务名称和数据库实例。tableDataSouce.Rows[i]["ServerName"]就是客户端能够获取的服务名称,tableDataSouce.Rows[i]["InstanceName"]能够获取数据库实例的名称。
(b).获得服务器上的数据库名称
2 {
3 using (IDbProvider provider = new SqlProvider())
4 {
5 if (string.IsNullOrEmpty(userid) && string.IsNullOrEmpty(password))
6 {
7 provider.ConnectionString = " server= " + servername + " ;database=master;Integrated Security=true " ;
8 }
9 else
10 {
11 provider.ConnectionString = " server= " + servername + " ;database=master;uid= " + userid + " ;pwd= " + password;
12 }
13 IBaseHelper baseHelper = new BaseHelper();
14 string sql = " select name from sysdatabases where dbid>4 " ;
15 return baseHelper.ExecuteTable(provider,sql);
16 }
17 }
sql server 数据库我们不能忘却的一个数据库那就是master数据库,这个数据库包含了整个服务所包含的大多数信息,其中该数据库中的SysDatabases表就记录了该服务中存在的数据库名称以及相关信息。该表中的子弹dbid记录的是数据库的编号。当dbid>4的时候查询得到的就是用户数据库,那小于4的就不用多说了,就是我们常见的那四个系统数据库了。
(c).查询数据库中的表
2 {
3 using (IDbProvider provider = new SqlProvider())
4 {
5 if (string.IsNullOrEmpty(userid) && string.IsNullOrEmpty(password))
6 {
7 provider.ConnectionString = " server= " + servername + " ;database= " + databasename + " ;Integrated Security=true " ;
8 }
9 else
10 {
11 provider.ConnectionString = " server= " + servername + " ;database= " + databasename + " ;uid= " + userid + " ;pwd= " + password;
12 }
13 IBaseHelper baseHelper = new BaseHelper();
14 string sql = " select name from sysobjects where type='U' " ;
15 return baseHelper.ExecuteTable(provider, sql);
16 }
17 }
在每个数据库中都隐含了一个表,我们在建立数据表的时候通常写一句代码
if exists (select * from sysobjects where name='tablename') 写到这里不多大家明白了,表Sysobjects,这张表存储了该数据库中其他表的表信息。我们可以查询该张表得到该数据库的其他表信息。当然如果要查询用户表我们就得指定条件了。该表的字段type='U' 的时候就说明该张表是用户表。
(d). 字段属性查询
代码
2 syscolumns.name as ColName ,
3 systypes.name as ColTypeName ,
4 syscolumns.length,
5 sys.extended_properties.value as Mark ,
6 AllowNull = case
7 when (syscolumns.isnullable = 0 ) then ' false '
8 else ' true ' end ,
9 IsAuto = case
10 when ( ( SELECT COLUMNPROPERTY ( OBJECT_ID ( ' TabRule ' ),syscolumns.name, ' IsIdentity ' )) = 1 )
11 then ' true '
12 else ' false ' end ,
13 IsPK = Case
14 when exists ( select 1 from sysobjects inner join sysindexes on sysindexes.name = sysobjects.name inner join sysindexkeys on sysindexes.id = sysindexkeys.id and sysindexes.indid = sysindexkeys.indid where xtype = ' PK ' and parent_obj = syscolumns.id and sysindexkeys.colid = syscolumns.colid )
15 then ' true '
16 else ' false ' end ,
17 IsIdentity = Case syscolumns.status when 128 then ' true ' else ' false ' end from syscolumns inner join systypes on ( syscolumns.xtype = systypes.xtype and systypes.name <> ' _default_ ' and systypes.name <> ' sysname ' ) left outer join sys.extended_properties on ( sys.extended_properties.major_id = syscolumns.id and minor_id = syscolumns.colid ) where syscolumns.id = ( select id from sysobjects where name = ' TabRule ' )
18 order by syscolumns.colid
上面的这段sql语句可以查询出表字段的详细信息。包括字段名称,数据类型,长度,主键等等信息。查询该信息是为了在生成实体的时候,匹配相应的规则。自己查阅了好多资料终于写出了这段sql语句。后来才发现其实网上有很多这种相关的代码,浪费了我好多时间。不过能写出了就好了。ColName 记录的是字段名称,ColTypeName记录的是字段类型,length记录了字段的长度,Mark记录了字段的描述信息,AllowNull 记录字段是否允许为空,IsAuto记录字段是否自动增长,IsPK 记录字段是否为主键,IsIdentity记录知道是否为标识列
2 string tableName = ddlTableNames.SelectedItem.ToString();
3 string connection = null ;
4 SqlConnection con = null ;
5 if (rbWindows.Checked)
6 {
7 connection = " server= " + servername + " ;database= " + ddlDataBaseName.SelectedItem.ToString() + " ;Integrated Security=true " ;
8 }
9 else
10 {
11 connection = " server= " + servername + " ;database=master;uid= " + txtUserName.Text + " ;pwd= " + txtPassword.Text;
12 }
13
14 try
15 {
16 con = new SqlConnection(connection);
17 con.Open();
18 StringBuilder sb = new StringBuilder( " select " );
19 sb.Append( " syscolumns.name as ColName , " );
20 sb.Append( " systypes.name as ColTypeName , " );
21 sb.Append( " syscolumns.length, " );
22 sb.Append( " sys.extended_properties.value as Mark , " );
23 sb.Append( " IsAuto=case " );
24 sb.Append( " when ( (SELECT COLUMNPROPERTY( OBJECT_ID(' " + tableName + " '),'syscolumns.name','IsIdentity')) =1) " );
25 sb.Append( " then 'true' else 'false' end, " );
26 sb.Append( " AllowNull=case " );
27 sb.Append( " when (syscolumns.isnullable=0) then 'false' " );
28 sb.Append( " else 'true' end, " );
29 sb.Append( " IsPK = Case " );
30 sb.Append( " when exists ( select 1 from sysobjects inner join sysindexes on sysindexes.name = sysobjects.name inner join sysindexkeys on sysindexes.id = sysindexkeys.id and sysindexes.indid = sysindexkeys.indid where xtype='PK' and parent_obj = syscolumns.id and sysindexkeys.colid = syscolumns.colid ) " );
31 sb.Append( " then 'true' else 'false' end , " );
32 sb.Append( " IsIdentity = Case syscolumns.status when 128 then 1 else 0 end from syscolumns inner join systypes on ( syscolumns.xtype = systypes.xtype and systypes.name <>'_default_' and systypes.name<>'sysname' ) left outer join sys.extended_properties on ( sys.extended_properties.major_id=syscolumns.id and minor_id=syscolumns.colid ) where syscolumns.id = (select id from sysobjects where name=' " + tableName + " ') " );
33 sb.Append( " order by syscolumns.colid " );
34 SqlCommand command = new SqlCommand(sb.ToString(), con);
35 SqlDataReader reader = command.ExecuteReader();
36 ddlTableNames.Items.Clear();
37 StringBuilder sbCode = new StringBuilder( "" );
38 sbCode.Append( " using System;\n " );
39 sbCode.Append( " using System.Collections.Generic;\n " );
40 sbCode.Append( " using System.Linq;\n " );
41 sbCode.Append( " using System.Text;\n " );
42 sbCode.Append( " using CommonData.Entity;\n " );
43 sbCode.Append( " using CommonData.Model.Core;\n " );
44 sbCode.Append( " \n " );
45 sbCode.Append( " namespace Entity\n " );
46 sbCode.Append( " {\n " );
47
48 sbCode.AppendFormat( " \t[Serializable]\n " );
49 sbCode.AppendFormat( " \t[TableAttribute(DBName = \ " \ " , Name = \ " { 0 }\ " , PrimaryKeyName = \ " @PrimaryKeyName\ " , IsInternal = false)]\n " , tableName);
50 sbCode.AppendFormat( " \tpublic class {0}:BaseEntity\n " , tableName.FirstToUpper(tableName));
51 sbCode.Append( " \t{\n " );
52 sbCode.AppendFormat( " \t\tpublic {0}()\n " , tableName.FirstToUpper(tableName));
53 sbCode.Append( " \t\t{\n " );
54 sbCode.Append( " \t\t}\n\n " );
55 string pkName = " Id " ;
56 while (reader.Read())
57 {
58 if (reader[ " IsPK " ].ToString() == " true " )
59 {
60 pkName = reader[ " ColName " ].ToString();
61 }
62 sbCode.AppendFormat( " \t\tprivate {0} {1};\n " , GetType(reader[ " ColTypeName " ].ToString()), reader[ " ColName " ].ToString().FirstToLower(reader[ " ColName " ].ToString()));
63 sbCode.AppendFormat( " \t\t[ColumnAttribute(Name = \ " { 0 }\ " , IsPrimaryKey = {1}, AutoIncrement = {2}, DataType = DataType.{3}, CanNull = {4})]\n " , reader[ " ColName " ].ToString(), reader[ " IsPK " ].ToString(), reader[ " IsAuto " ].ToString(), GetDataType(reader[ " ColTypeName " ].ToString()), reader[ " AllowNull " ].ToString());
64 sbCode.AppendFormat( " \t\tpublic {0} {1}\n " , GetType(reader[ " ColTypeName " ].ToString()), reader[ " ColName " ].ToString().FirstToUpper(reader[ " ColName " ].ToString()));
65 sbCode.Append( " \t\t{\n " );
66 sbCode.Append( " \t\t\tget { return " + "" .FirstToLower(reader[ " ColName " ].ToString()) + " ; }\n " );
67 sbCode.Append( " \t\t\tset { " + "" .FirstToLower(reader[ " ColName " ].ToString()) + " = value; }\n " );
68 sbCode.Append( " \t\t}\n\n " );
69 }
70 sbCode.Append( " \t}\n " );
71 sbCode.Append( " }\n " );
72 sbCode.Replace( " @PrimaryKeyName " , pkName);
73 rtxtCode.Text = sbCode.ToString();
74 }
75 catch
76 {
77 MessageBox.Show( " 连接失败 " );
78 }
79 finally
80 {
81 if (con != null )
82 {
83 con.Close();
84 }
85 }
这段代码就是实现了上面实体的生成,这里已经没有多少东西了,只是一个拼字符串的工作。只要仔细认真就可以了。到此位置该介绍的东西就介绍完了。
当我做完这个东西的时候发现代码生成其核心并不是很难,掌握这些要点就能够写出一个实用的代码生成器。学习东西也是如此,掌握了核心,无论外形怎么变都是万变不离其宗。在后续的文章中将讲解个人框架了,相对于年前的那个来说这个ORM有了很大的改进,以后慢慢与大家分享自己的程序心得。
源码其实很简单,对于自动获取服务器名称这个功能还没有添加,还有那个自动附加数据库文件。这里上传一下源码,大家有兴趣看一下。 /Files/qingyuan/CodeCreate.zip