C#实现简单ORM
2017-08-09 20:08
197 查看
闲话
当天在布置这个任务的时候,一听,实现增删改查,我还天真的想:作业这么简单的么?可是到了实际开始操作的时候才恍然大悟,只恨自己太年轻啊。对于ORM,我的认识还停留在学习java的室友使用的Hibernate上,曾经有一份Entity Framework的教程放在我的面前,我没有珍惜,如果给我一次重新选择的机会,我会好好学习,如果非要给这个学习加上一个权限,我希望是一万年。开个玩笑,虽然对相关知识的不熟悉导致初期很吃力,但终归是完成了,现在缝缝补补,终于看起来像点样子了,这里就对这次ORM作业的完成过程做一个总结,回顾在这个过程中遇到的问题以及解决办法,也趁此机会梳理一下思路。拿到任务代码的框架,一打开,迎面而来的便是绝望的气息。摆在我面前的是那四个空空如也待实现的方法,而自己完全不知道从哪里下手,于是乎,打开示例代码,网上了解ORM,照着葫芦画瓢,开始code。通过简单的了解,整理出了几点实现这个任务需要的知识,对象关系映射,怎么个映射法?定义的实体类如何和数据表联系起来?反射和Attribute怎样实现前面的问题?这些都需要一一的去解决。
反射和Attribute
突然一来就开始讲反射和Attribute,可能觉得有点突兀,那为什么要在一开始就提到这两个东西呢?由于实现的是简单ORM,所以采用的是一个类映射到一个表的模式,实体类中的属性(Property)和数据库表里的字段一一对应。为了到达这样效果,我们就需要采用反射和Attribute相互合作的模式。Lewis.X.Liu (g-mis.cncd02.Newegg) 42241 > 2017/07/21 > C#实现简单ORM个人心得 > 表关系.jpg
Attribute:MSDN的定义为:公共语言运行时允许添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型、字段、方法和属性等。Attributes和Microsoft .NET Framework文件的元数据(metadata)保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。所以Attribute就是用来为元素添加附加信息的,可以让我们定义具体的映射规则,而通过反射我们又可以动态获取对象信息的能力来获得这些规则。从而实现了实体类到具体的数据库表的映射。特别指出,Attribute和Property中文都为属性,但是Property是实体类中的属性,而Attribute是用于提供附加信息,为坐区分,叫作特性。在本次代码中,定义了三个Attribute,通过他们来描述部分数据库信息。
//表特性 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class TableAttribute : System.Attribute { public string tableName { get; set; } public TableAttribute(string name) { tableName = name; } } //列特性 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class ColumnAttribute : System.Attribute { public bool IsPrimaryKey { get; set; } public string Name { get; set; } public Type DataType { get; set; } public ColumnAttribute(string name) { Name = name; } } //主键特性 [AttributeUsage(AttributeTargets.Class)] public class PrimaryKeyAttribute : System.Attribute { public string ID { get; set; } public PrimaryKeyAttribute(string name) { ID = name; } }
同时,利用Attribute为实体类添加与数据库字段的对应关系:
[Table("dbo.LewisTable")] public class User { [Column("ID",DataType =typeof(int),IsPrimaryKey =true)] public int ID { get; set; } [Column("Name")] public string Name { get; set; } [Column("InDate")] public DateTime InDate { get; set; } [Column("InUser")] public string InUser { get; set; } [Column("LastEditDate")] public DateTime? LastEditDate { get; set; } [Column("LastEditUser")] public string LastEditUser { get; set; } } 接下来,就轮到反射上场了,通过反射获得从实体映射出数据库表: //参数表传入的实体类 TEntity entity = new TEntity(); //获得类型 Type entityType = entity.GetType(); //获取属性 var properties = entityType.GetProperties(); //实体属性和表字段匹配 foreach(var propertyInfo in properties) { var attribute = (ColumnAttribute)propertyInfo.GetCustomAttributes(typeof(ColumnAttribute), true)[0]; Console.Write("{0,15}",attribute.Name+":\t\t"); Console.Write(reader[propertyInfo.Name]+"\n"); }
生成SQL语句
在完成了上述的工作以后,我们便要开始考虑操作数据库的问题,这个时候要考虑这几个方面:
如何获得要操作的表名
如何获得主键
如何获得要操作的字段
如何获得字段的值
where子句的条件如何获得
如何获得完整的SQL语句
可空字段如何处理
获取表名
在上面提到的Attribute特性表中,包含了一个TableAttribute,通过这个特性表,我们便可以获得数据库表名称
//获取表名 object[] tableName = entityType.GetCustomAttributes(typeof(TableAttribute), false); if (tableName != null) string name = (TableAttribute)tableName[0])).tableName ;
获得主键
当我们进行数据更新的时候,如果有要求不该能更新主键,这时候我们就需要将主键找出来,不把他拼接进入SQL语句中。具体做法和获取表名类似,我们添加了一个主键的特性类,通过IsPrimaryKey就能判断一个字段是否为主键。同时,如果更新语句使用主键ID作为where子句的条件,在进行判断的时候,我们还可以获取主键的值备用,之后可以加入参数列表。
获得字段名称以及值
字段是sql语句中很大的一个部分,他是操作的具体内容。我们通过反射获得字段名称,利用了GetProperties()和GetCustomAttributes()方法。获取了字段名称后,如果是要修改数据库的情况,我们还需要获得实体传入的字段值,这个值通过GetValue()方法获得,与之对应还有SetValue()方法,将在之后空值处理时提到。
PropertyInfo[] proinfos = entityType.GetProperties(); foreach (PropertyInfo property in propertyInfos) { object[] myobj = property.GetCustomAttributes(typeof(ColumnAttribute), false); if (myobj != null) { //获取字段名称 string name = ((ColumnAttribute)myobj[0]).Name; //可空值的处理 if(entityType.GetProperty(name).GetValue(entity,null)!=null) { sqlBegin.Append(name + ","); //获取值 sqlEnd.Append("'" + entityType.GetProperty(name).GetValue(entity, null) + "',"); } } }
可空字段的处理
在本次的任务的数据库表中包含了两个可空字段,类型分别为string和DateTime。string类型是引用类型,所以可以为空值,但是DateTime是一个值类型,那么问题便来了,怎么满足将这个字段设置为空的需求呢?最后,寻得DataTime?的解决办法,它为DataTime添加了null值,从而达到了我们的要求。在具体的业务逻辑中,如果实体传入的内容中可空字段并未初始化,也就是值为空,那么便不将这个字段拼接进入SQL中。也可以在参数列表中将其复制为空,这样不会存在字段和参数不匹配的问题,也将空值写入了表中。
//可空值的处理, if(entityType.GetProperty(name).GetValue(entity,null)!=null) { sqlBegin.Append(name + ","); qlEnd.Append("'" + entityType.GetProperty(name).GetValue(entity, null) + "',"); } //使用参数列表的情况 else if(LaDate==true&&LaUser==true) { rowCount = SqlHelper.Instance.ExecuteNonQuery(conn, upSql, new List<SqlParameter>() { new SqlParameter( "@ID",SqlDbType.Int){Value=IDValue }, new SqlParameter( "@Name",SqlDbType.NVarChar,50){ Value=entityType.GetProperty("Name").GetValue(entity, null)}, new SqlParameter( "@InDate",SqlDbType.DateTime){ Value=entityType.GetProperty("InDate").GetValue(entity, null)}, new SqlParameter( "@InUser",SqlDbType.VarChar,15){ Value=entityType.GetProperty("InUser").GetValue(entity, null)}, new SqlParameter( "@LastEditDate",SqlDbType.DateTime){ Value=DBNull.Value}, ew SqlParameter( "@LastEditUser",SqlDbType.VarChar,15){ Value=DBNull.Value}, });
SQL语句拼接
在获得了SQL语句的各组成部分后,就需要将各部分内容拼接起来,构成完整的SQL语句。这里就要提到string和stringBuilder了。string类型有一个不可变特性,所以string对象是之读的,这时候有人会说了,我明明可以改变它的值啊。这个表面上看起来值的改变,实际上是一个新对象的建立,而原有的string对象并未改变。所以,在我们拼接SQL语句的使用场景下,我选择使用StingBuilder。因为每次创建字符串时候,string会先在池中找,如果有相同的,则会拿来用,如果没找到,则会创建一个新的字符串对象,因此在做字符串拼接的时候,特别是循环拼接,实际上在不断的在字符串池中创建字符串对象,因此这样效率就很慢了。
StringBuilder sqlBegin = new StringBuilder(); sqlBegin.Append(""); //拼接Sql语句 StringBuilder sqlEnd = new StringBuilder(); sqlEnd.Append("VALUES("); if(entityType.GetProperty(name).GetValue(entity,null)!=null) { sqlBegin.Append(name + ","); sqlEnd.Append("'" + entityType.GetProperty(name).GetValue(entity, null) + "',"); } sqlBegin.Append(")"); sqlBegin.Replace(",)", ")"); //修正尾部多余的符号 sqlEnd.Append(")"); sqlEnd.Replace(",)", ") SELECT SCOPE_IDENTITY()"); string sql = "INSERT INTO " + sqlBegin.ToString() + sqlEnd.ToString();
至此,任务就基本完成了,传入实体对象,调用即可。
结语
思路凌乱,希望各位大佬批评指正,感激不尽。
相关文章推荐
- C#基础---浅谈XML读取以及简单的ORM实现
- C#基础---浅谈XML读取以及简单的ORM实现
- 用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通
- 利用字典实现Python中简单的ORM映射
- JavaScript和C#通用gb2312和utf8编码解码函数简单实现
- 用C#实现简单的打字闯关游戏
- C#实现的简单验证码识别实例
- C#实现的简单验证码识别实例
- C#一颗简单多叉树的实现(原理、广度优先遍历、深度优先遍历)
- jquery+ajax+C#实现无刷新操作数据库数据的简单实例
- [C#]实现简单发送邮件
- C#实现简单的 Ping 的功能,用于测试网络是否已经联通
- WinForm实现简单的拖拽文件到出题的功能(C#)(3)
- 基于C#中的Trace实现一个简单的日志系统
- C#一颗简单多叉树的实现(原理、广度优先遍历、深度优先遍历)
- 用C#实现的Ping模块,简单实用
- Unity, C# ,TCP实现的一个简单的对话同步功能
- 简单实现C#生成Excel 2007文件并下载
- C#实现一个最简单的HTTP服务器
- 一个简单的自动化测试架构的实现(C#)