ORM映射框架总结--代码生成器
2010-03-19 15:38 贺臣 阅读(4815) 评论(4) 编辑 收藏 举报年前发布了一些文章,是关于.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