最近在研究ORM,尝试着自己开发了一个简单的ORM。我个人不喜欢EF因为跟不上EF升级太快了,再说公司里还停留在c# 3.5时代,对于NHibernate配置太复杂看到就头晕,就心生自己做一个ORM的念头,现在把开发过程中的点点滴滴记录下来,供自己和新手参考,大神请直接忽略这篇文章。
ORM(object relation mapping)对象关系映射,常用来映射数据库里表、字段等信息。ORM的原理无非就是用配置文件、Attribute来映射数据关系。我们一步步来来创建ORM,定义一个实体类,然后用反射获得类名(表名)、属性(对应是字段名称),例子如下:
CREATE TABLE [dbo].[User]( [UserId] [int] NOT NULL primary key, [Email] [nvarchar](100) NULL, [CreatedTime] [datetime] NULL )
在这里我创建一张表,其中UserId是主键,为什么不定义成自增长的呢?请保留这个疑问,继续看下去。表创建好了,下面就是定义一个类,如下:
public class User { public int UserId { get; set; } public string Email { get; set; } public DateTime CreatedTime { get; set; } }
现在我们要对这个类写一个INSERT操作,直接传入User对象然后插入到数据库。我们要用系统自动构建SQL语句,不需要手写SQL语句,在这我们先分析下INSERT INTO SQL语句。
INSERT INTO 表名 (字段1 ,字段2 ,字段3...) VALUES 值1, 值2, 值3...
我们需要知道表名、字段名和值才能够完成SQL拼接,怎么得到这些信息呢,万能的反射该出场。
string className = typeof(User).Name; Console.WriteLine("类名:{0}", className); Console.WriteLine("-----获取属性信息-----"); var properties = typeof(User).GetProperties(); foreach (var item in properties) { Console.WriteLine("属性名:{0}",item.Name); } Console.WriteLine("-----end-----");
运行结果如下
LOOK,User类的信息都显示出来了,属性的值该如何显示?也就是SQL语句的VALUE,还是要靠万能的反射,请看官门继续往下看。
string className = typeof(User).Name; Console.WriteLine("类名:{0}", className); var properties = typeof(User).GetProperties(); User user = new User() { UserId = 123, Email = "abc@123.com", CreatedTime = DateTime.Now }; foreach (var item in properties) { Console.WriteLine("{0}:{1}", item.Name, item.GetValue(user, null)); }
类名、属性名、属性值我们都得到了,那下面要做的就是拼写SQL语句了
public static void Main() { PritSql(new User() { UserId = 123, Email = "abc@123.com", CreatedTime = DateTime.Now }); } public static void PritSql(User user) { string className = typeof(User).Name; var properties = typeof(User).GetProperties(); StringBuilder sql = new StringBuilder(); sql.Append("INSERT INTO ").Append(className).Append("("); for (int i = 0; i < properties.Length; i++) { var pi = properties[i]; if (i > 0) sql.Append(","); sql.Append(pi.Name); } sql.Append(") VALUES ("); for (int i = 0; i < properties.Length; i++) { var pi = properties[i]; if (i > 0) sql.Append(","); sql.Append("'").Append(pi.GetValue(user,null)).Append("'"); } sql.Append(")"); Console.WriteLine(sql); }
为避免SQL注入,我把SQL改成参数的信息
INSERT INTO 表名 (字段1 ,字段2 ,字段3...) VALUES @p1, @p2, @p3...
修改后的c#代码
public static void Main() { PritParameterSql(new User() { UserId = 123, Email = "abc@123.com", CreatedTime = DateTime.Now }); } public static int PritParameterSql<T>(T user) { Dictionary<string, object> parameters = new Dictionary<string, object>(); string className = typeof(T).Name; var properties = typeof(T).GetProperties(); StringBuilder sql = new StringBuilder(); sql.Append("INSERT INTO [").Append(className).Append("]("); for (int i = 0; i < properties.Length; i++) { var pi = properties[i]; if (i > 0) sql.Append(","); sql.Append(pi.Name); } sql.Append(") VALUES ("); for (int i = 0; i < properties.Length; i++) { var pi = properties[i]; if (i > 0) sql.Append(","); sql.Append("@p").Append(i); parameters.Add("@p" + i, pi.GetValue(user, null)); } sql.Append(")"); Console.WriteLine(sql); SqlConnection conn = new SqlConnection(connectionString); var cmd = conn.CreateCommand(); cmd.CommandText = sql.ToString(); foreach (var item in parameters) { var pa = cmd.CreateParameter(); pa.ParameterName = item.Key; pa.Value = item.Value ?? DBNull.Value; cmd.Parameters.Add(pa); } conn.Open(); return cmd.ExecuteNonQuery(); }
执行后的结果
还记得前面提到为什么UserId不设置成自增长吗?因为这种单纯的反射无法识别哪个是自增长字段,如果想用自增长主键怎么办?请看后续文章。