尝试手写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;
}
}