尝试手写orm框架
Posted wktang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尝试手写orm框架相关的知识,希望对你有一定的参考价值。
前言:
在使用各种的orm框架的过程中,菜鸟的我始终没有搞懂底层实现技术,最近刚好没事找了些视频和资料了解一点皮毛,想记录下,大家勿喷。
所谓的ORM(Object Relational Mapping) 对象关系映射 官方解释是通过使用描述对象和数据库之间映射的元数据,将面向对象程序的对象自动持久化到关系数据库中。
个人理解就是一个数据库访问的帮助类,可以让我们不用手写sql,就完成数据库的访问
使用的技术: 泛型、反射、特性、扩展
摸索步骤:
step1
新建项目,建几个类库,大家熟悉的三层。
step2:
在数据访问层新建一个sqlhelper 类;
2.1 添加一个数据查询的方法,还需要添加model层添加SysUser,完成实体与表映射,实现代码 如下
1 public class SysUser 2 { 3 public long Id { get; set; } 4 public string Login_Name { get; set; } 5 public string Name { get; set; } 6 public string Icon { get; set; } 7 public string Password { get; set; } 8 public string Salt { get; set; } 9 public string Tel { get; set; } 10 public string Email { get; set; } 11 public SByte Locked { get; set; } 12 public DateTime Create_Date { get; set; } 13 public long Create_By { get; set; } 14 public DateTime Update_Date { get; set; } 15 public long Update_By { get; set; } 16 public string Remarks { get; set; } 17 public SByte Del_Flag { get; set; } 18 }
1 public SysUser QueryUser(string id) 2 { 3 Type type = typeof(SysUser); 4 SysUser sysUser = new SysUser(); 5 using (var con = new mysqlConnection(strConnection)) 6 { 7 con.Open(); 8 string strSql = $"select Id,Login_Name,nick_name,Icon,Password,Salt,Tel,Email,Locked,Create_Date,Create_By,Update_Date,Update_By,Remarks,Del_Flag from sys_user wherer id={id}"; 9 MySqlCommand sqlCommand = new MySqlCommand(strSql, con); 10 11 MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); 12 13 if(mySqlDataReader.Read()) 14 { 15 if (mySqlDataReader.Read()) 16 { 17 foreach (var item in type.GetProperties()) 18 { 19 item.SetValue(sysUser, mySqlDataReader[item.Name] is DBNull ? null : mySqlDataReader[item.Name]); 20 } 21 } 22 23 } 24 25 } 26 27 return sysUser; 28 }
2.2 上述代码只能对一个表进行查询,需要满足不同表查询可以使用泛型
a. 反射完成动态sql的拼接
Type type=typeof(T); string tableNam=type.Name; string colums=string.join(",",type.GetProperties().Select(p=>$"{p.Name}"));
string strSql = $"select {colums} from {tableName} where id={id}";
b.ado.net 完成数据库查询
c.反射完成数据动态绑定
e.可空值类型处理
实现代码
1 public T QueryById<T>(string id) 2 { 3 Type type = typeof(T); 4 5 T t = Activator.CreateInstance<T>();//创建实体 6 string tableName = type.Name; 7 string colums = string.Join(",", type.GetProperties().Select(p => $"{p.Name}"));//拼接查询字段 8 9 using (var con = new MySqlConnection(strConnection)) 10 { 11 con.Open(); 12 string strSql = $"select {colums} from {tableName} where id={id}"; 13 14 MySqlCommand sqlCommand = new MySqlCommand(strSql, con); 15 MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); 16 17 if (mySqlDataReader.Read()) 18 { 19 foreach (var item in type.GetProperties()) 20 { 21 item.SetValue(t, mySqlDataReader[item.Name] is DBNull ? null : mySqlDataReader[item.Name]);//需要添加Null 判断 22 } 23 return t; 24 } 25 else 26 { 27 return default(T); 28 } 29 } 30 }
存在问题: 实体类名与数据库表名不一致、实体属性名与数据表字段名不一致
解决上面两个问题 可以使用特性(解释: 特性本质就是一个类,间接或直接继承Attribute 就是一个特性,其为目标元素提供关联的附加信息,并在运行时以反射的方式来获取附加信息)
相关代码如下
抽象类
1 public abstract class AbstractMappingAttribute: Attribute 2 { 3 private string _mappingName; 4 5 public AbstractMappingAttribute(string mappingName) 6 { 7 this._mappingName = mappingName; 8 } 9 10 public string GetMappingName() 11 { 12 return this._mappingName; 13 } 14 }
实体类名与表名不一致时
1 [AttributeUsage(AttributeTargets.Class)] 2 public class MappingTableAttribute : AbstractMappingAttribute 3 { 4 public MappingTableAttribute(string tableName) : base(tableName) 5 { 6 7 } 8 }
实体属性与表字段不一致时
1 [AttributeUsage(AttributeTargets.Property)] 2 public class MappingColumAttribute : AbstractMappingAttribute 3 { 4 public MappingColumAttribute(string colName) : base(colName) 5 { 6 7 } 8 }
注意: 在使用特性须加上本特性类作用的范围,简单理解就是用在类上面还是类的属性或行为上。
实体类使用自定义特性代码如下
1 [MappingTableAttribute("sys_user")] 2 public class SysUser 3 { 4 public long Id { get; set; } 5 public string Login_Name { get; set; } 6 [MappingColumAttribute("nick_name")] 7 public string Name { get; set; } 8 public string Icon { get; set; } 9 public string Password { get; set; } 10 public string Salt { get; set; } 11 public string Tel { get; set; } 12 public string Email { get; set; } 13 public SByte Locked { get; set; } 14 public DateTime Create_Date { get; set; } 15 public long Create_By { get; set; } 16 public DateTime Update_Date { get; set; } 17 public long Update_By { get; set; } 18 public string Remarks { get; set; } 19 public SByte Del_Flag { get; set; } 20 }
怎么获取实体的自定义特性描述的信息?
这里面需要用到扩展方法
1 public static class MappingAttributeExtend 2 { 3 public static string GetMappingName<T>(this T t) where T : MemberInfo 4 { 5 if (t.IsDefined(typeof(AbstractMappingAttribute), true)) 6 { 7 AbstractMappingAttribute abstractMappingAttribute = t.GetCustomAttribute<AbstractMappingAttribute>(); 8 return abstractMappingAttribute.GetMappingName(); 9 } 10 else 11 { 12 return t.Name; 13 } 14 } 15 }
2.3 经过2.2一步步优化最终通用查询方法代码如下
1 public T QueryById<T>(string id) 2 { 3 Type type = typeof(T); 4 5 T t = Activator.CreateInstance<T>();//创建实体 6 string tableName = type.GetMappingName(); 7 string colums = string.Join(",", type.GetProperties().Select(p => $"{p.GetMappingName()}"));//拼接查询字段 8 9 using (var con = new MySqlConnection(strConnection)) 10 { 11 con.Open(); 12 string strSql = $"select {colums} from {tableName} where id={id}"; 13 14 MySqlCommand sqlCommand = new MySqlCommand(strSql, con); 15 MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); 16 17 if (mySqlDataReader.Read()) 18 { 19 foreach (var item in type.GetProperties()) 20 { 21 item.SetValue(t, mySqlDataReader[item.GetMappingName()] is DBNull ? null : mySqlDataReader[item.GetMappingName()]); 22 } 23 return t; 24 } 25 else 26 { 27 return default(T); 28 } 29 } 30 }
step3:
完成了简单查询,新增大同小异,实现代码如下
1 public bool Insert<T>(T t) 2 { 3 Type type = typeof(T); 4 5 string table = type.GetMappingName(); 6 string colum = string.Join(",", type.GetProperties().Select(p => $"{p.GetMappingName()}")); 7 string value = string.Join(",", type.GetProperties().Select(p => $"‘{p.GetValue(t)}‘")); 8 9 using (var con = new MySqlConnection(strConnection)) 10 { 11 con.Open(); 12 string strSql = $"Insert into {table} ({colum}) values ({value}) "; 13 14 MySqlCommand mySqlCommand = new MySqlCommand(strSql, con); 15 16 return mySqlCommand.ExecuteNonQuery() == 1; 17 } 18 19 }
以上是关于尝试手写orm框架的主要内容,如果未能解决你的问题,请参考以下文章
手写Mybatis框架(半自动ORM框架)-- 01: 分析实现Mybatis的三个阶段