尝试手写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  }
View Code
技术图片
 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  }
View Code

 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 }
View Code

 存在问题: 实体类名与数据库表名不一致、实体属性名与数据表字段名不一致 

 解决上面两个问题 可以使用特性(解释: 特性本质就是一个类,间接或直接继承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   }
View Code

实体类名与表名不一致时 

技术图片
1  [AttributeUsage(AttributeTargets.Class)]
2   public class MappingTableAttribute : AbstractMappingAttribute
3  {
4         public MappingTableAttribute(string tableName) : base(tableName)
5         {
6 
7         }
8  }
View Code

实体属性与表字段不一致时 

技术图片
1  [AttributeUsage(AttributeTargets.Property)]
2  public class MappingColumAttribute : AbstractMappingAttribute
3  {
4         public MappingColumAttribute(string colName) : base(colName)
5         {
6 
7         }
8  }
View Code

注意: 在使用特性须加上本特性类作用的范围,简单理解就是用在类上面还是类的属性或行为上。

实体类使用自定义特性代码如下

技术图片
 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     }
View Code

 怎么获取实体的自定义特性描述的信息? 

  这里面需要用到扩展方法

技术图片
 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 }
View Code

 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  }
View Code

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  }
View Code

 

以上是关于尝试手写orm框架的主要内容,如果未能解决你的问题,请参考以下文章

基于Spring JDBC手写定制自己的ORM框架

基于Spring JDBC手写定制自己的ORM框架

手写MyBatis ORM框架实践

手写Mybatis框架(半自动ORM框架)-- 01: 分析实现Mybatis的三个阶段

面试官让我现场手写MyBatis框架,我10分钟就搞定了!!(全程实战,建议收藏)

JAVAEE框架技术之7-myBatis ORM框架入门基础CRUD