尝试手写orm框架
前言:
在使用各种的orm框架的过程中,菜鸟的我始终没有搞懂底层实现技术,最近刚好没事找了些视频和资料了解一点皮毛,想记录下,大家勿喷。
所谓的ORM(Object Relational Mapping) 对象关系映射 官方解释是通过使用描述对象和数据库之间映射的元数据,将面向对象程序的对象自动持久化到关系数据库中。
个人理解就是一个数据库访问的帮助类,可以让我们不用手写sql,就完成数据库的访问
使用的技术: 泛型、反射、特性、扩展
摸索步骤:
step1
新建项目,建几个类库,大家熟悉的三层。
step2:
在数据访问层新建一个sqlhelper 类;
2.1 添加一个数据查询的方法,还需要添加model层添加SysUser,完成实体与表映射,实现代码 如下
public class SysUser { public long Id { get; set; } public string Login_Name { get; set; } public string Name { get; set; } public string Icon { get; set; } public string Password { get; set; } public string Salt { get; set; } public string Tel { get; set; } public string Email { get; set; } public SByte Locked { get; set; } public DateTime Create_Date { get; set; } public long Create_By { get; set; } public DateTime Update_Date { get; set; } public long Update_By { get; set; } public string Remarks { get; set; } public SByte Del_Flag { get; set; } }
public SysUser QueryUser(string id) { Type type = typeof(SysUser); SysUser sysUser = new SysUser(); using (var con = new MySqlConnection(strConnection)) { con.Open(); 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}"; MySqlCommand sqlCommand = new MySqlCommand(strSql, con); MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); if(mySqlDataReader.Read()) { if (mySqlDataReader.Read()) { foreach (var item in type.GetProperties()) { item.SetValue(sysUser, mySqlDataReader[item.Name] is DBNull ? null : mySqlDataReader[item.Name]); } } } } return sysUser; }
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.可空值类型处理
实现代码
public T QueryById<T>(string id) { Type type = typeof(T); T t = Activator.CreateInstance<T>();//创建实体 string tableName = type.Name; string colums = string.Join(",", type.GetProperties().Select(p => $"{p.Name}"));//拼接查询字段 using (var con = new MySqlConnection(strConnection)) { con.Open(); string strSql = $"select {colums} from {tableName} where id={id}"; MySqlCommand sqlCommand = new MySqlCommand(strSql, con); MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); if (mySqlDataReader.Read()) { foreach (var item in type.GetProperties()) { item.SetValue(t, mySqlDataReader[item.Name] is DBNull ? null : mySqlDataReader[item.Name]);//需要添加Null 判断 } return t; } else { return default(T); } } }
存在问题: 实体类名与数据库表名不一致、实体属性名与数据表字段名不一致
解决上面两个问题 可以使用特性(解释: 特性本质就是一个类,间接或直接继承Attribute 就是一个特性,其为目标元素提供关联的附加信息,并在运行时以反射的方式来获取附加信息)
相关代码如下
抽象类
public abstract class AbstractMappingAttribute: Attribute { private string _mappingName; public AbstractMappingAttribute(string mappingName) { this._mappingName = mappingName; } public string GetMappingName() { return this._mappingName; } }
实体类名与表名不一致时
[AttributeUsage(AttributeTargets.Class)] public class MappingTableAttribute : AbstractMappingAttribute { public MappingTableAttribute(string tableName) : base(tableName) { } }
实体属性与表字段不一致时
[AttributeUsage(AttributeTargets.Property)] public class MappingColumAttribute : AbstractMappingAttribute { public MappingColumAttribute(string colName) : base(colName) { } }
注意: 在使用特性须加上本特性类作用的范围,简单理解就是用在类上面还是类的属性或行为上。
实体类使用自定义特性代码如下
[MappingTableAttribute("sys_user")] public class SysUser { public long Id { get; set; } public string Login_Name { get; set; } [MappingColumAttribute("nick_name")] public string Name { get; set; } public string Icon { get; set; } public string Password { get; set; } public string Salt { get; set; } public string Tel { get; set; } public string Email { get; set; } public SByte Locked { get; set; } public DateTime Create_Date { get; set; } public long Create_By { get; set; } public DateTime Update_Date { get; set; } public long Update_By { get; set; } public string Remarks { get; set; } public SByte Del_Flag { get; set; } }
怎么获取实体的自定义特性描述的信息?
这里面需要用到扩展方法
public static class MappingAttributeExtend { public static string GetMappingName<T>(this T t) where T : MemberInfo { if (t.IsDefined(typeof(AbstractMappingAttribute), true)) { AbstractMappingAttribute abstractMappingAttribute = t.GetCustomAttribute<AbstractMappingAttribute>(); return abstractMappingAttribute.GetMappingName(); } else { return t.Name; } } }
2.3 经过2.2一步步优化最终通用查询方法代码如下
public T QueryById<T>(string id) { Type type = typeof(T); T t = Activator.CreateInstance<T>();//创建实体 string tableName = type.GetMappingName(); string colums = string.Join(",", type.GetProperties().Select(p => $"{p.GetMappingName()}"));//拼接查询字段 using (var con = new MySqlConnection(strConnection)) { con.Open(); string strSql = $"select {colums} from {tableName} where id={id}"; MySqlCommand sqlCommand = new MySqlCommand(strSql, con); MySqlDataReader mySqlDataReader = sqlCommand.ExecuteReader(); if (mySqlDataReader.Read()) { foreach (var item in type.GetProperties()) { item.SetValue(t, mySqlDataReader[item.GetMappingName()] is DBNull ? null : mySqlDataReader[item.GetMappingName()]); } return t; } else { return default(T); } } }
step3:
完成了简单查询,新增大同小异,实现代码如下
public bool Insert<T>(T t) { Type type = typeof(T); string table = type.GetMappingName(); string colum = string.Join(",", type.GetProperties().Select(p => $"{p.GetMappingName()}")); string value = string.Join(",", type.GetProperties().Select(p => $"‘{p.GetValue(t)}‘")); using (var con = new MySqlConnection(strConnection)) { con.Open(); string strSql = $"Insert into {table} ({colum}) values ({value}) "; MySqlCommand mySqlCommand = new MySqlCommand(strSql, con); return mySqlCommand.ExecuteNonQuery() == 1; } }