ORM映射框架总结--代码生成器

年前发布了一些文章,是关于.NET数据操作(点击查看)的。刚开始学习编程的时候,总感觉Java中的Hibernate 功能好强大,现在也不可否认它的确强大,特别是它在数据关系处理上,却是那样的让人称叹。

         当我那时还不知道.net 中的Linq的时候,一直想自己能够简单的写个ORM映射框架。去年花费了几个月的业务时间终于算是整出来了,一些基本操作都能够实现了,自己号称从数据库操作冗余代码中解脱出来,其实自己很天真,那个框架是多么的不完善。总结了一下,最近超体力透支的准备将其扩展,同时也碰到了一下很头疼的问题,那就是那个实体的生成。

每次都要去受到生成那些代码,是一件非常消耗体力的事情,而李老师的动软代码生成器也不能生成我想要的代码,于是自己查阅了一些资料,写了一个自己需要的代码生成器,在此共享一下。

 

1.效果图 

看东西总是效果图比较直接,首先看看这个代码生成器的效果图。

ORM映射框架总结--代码生成器 

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. 生成代码展示

ORM映射框架总结--代码生成器代码

 1 using System;
 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).获取数据库服务名

 ORM映射框架总结--代码生成器代码

 1 public static IList<string> GetSource()
 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).获得服务器上的数据库名称

ORM映射框架总结--代码生成器ORM映射框架总结--代码生成器代码
 1 public static DataTable GetDataBase(string servername, string userid, string password)
 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).查询数据库中的表

ORM映射框架总结--代码生成器ORM映射框架总结--代码生成器代码
 1 public static DataTable GetTable(string servername, string databasename, string userid, string password)
 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). 字段属性查询ORM映射框架总结--代码生成器

代码

 1 select 
 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=0then 'false'
 8 else 'true' end,
 9 IsAuto=case
10     when ( (SELECT COLUMNPROPERTYOBJECT_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记录知道是否为标识列

ORM映射框架总结--代码生成器ORM映射框架总结--代码生成器代码
 1 string servername = ddlServerName.SelectedItem.ToString();
 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

 

上一篇:精确记算程序的运行时间或者某段代码的运行时间


下一篇:JAVA之旅(四)——面向对象思想,成员/局部变量,匿名对象,封装 , private,构造方法,构造代码块