使用泛型和反射实现一个简单的ORM框架
什么是ORM框架?
ORM框架是连接数据库与实体类帮助程序对接数据库的框架,类似于三层架构的数据访问层,但ORM框架可以根据数据库生成实体类,或者根据实体类生成数据库,解决了数据库与实体类不匹配的问题,而且,使用ORM框架可以很大程度的提高开发效率,省去了开发人员用在编写数据访问层花费的时间。
泛型与反射概述
关于泛型在之前的文章就有说过,还不了解的同学请点这里,这里重点说说反射,反射可以使程序读取自身并进行运行时修改,且可以调用读取到的程序内的方法,或者创建读取到的类。先说一下一个程序是如何从编译到运行的,C#代码首先会被编译器编译为DLL或者EXE程序然后经过CLR/JIT的编译成为计算机可以理解的二进制编码程序,而编译为DLL/EXE时会列出程序清单(元数据清单)matedata和中间语言IL,反射利用的就是这个DLL/EXE文件获得程序的数据。
基本用法:
获取DLL:
//(1)LoadFrom:dll全名称,需要后缀 Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); //(2)LoadFile:全路径,需要dll后缀 //Assembly assembly1 = Assembly.LoadFile(@"dll文件全路径"); //(3)Load:dll名称 不需要后缀 //Assembly assembly2 = Assembly.Load("Business.DB.SqlServer");
获取类型:
//2、获取某一个具体的类型,参数需要是类的全名称 Type type1 = assembly.GetType("Business.DB.SqlServer.SqlServerHelper");
创建对象:
//3、创建对象 //(1)直接传类型 object? oInstance = Activator.CreateInstance(type1); //(2)重载方法,传dll的全名称 //object? oInstanc1= Activator.CreateInstance("Business.DB.SqlServer.dll", "Business.DB.SqlServer.SqlServerHelper"); //a.oInstance.Query();//报错了:因为oInstance当做是一个object类型,object类型是没有Query方法;C#语言是一种强类型语言;编译时决定你是什么类型,以左边为准;不能调用是因为编译器不允许;实际类型一定是SqlServerHelper; //b.如果使用dynamic 作为类型的声明,在调用的时候,没有限制; //c.dynamic :动态类型:不是编译时决定类型,避开编译器的检查;运行时决定是什么类型 //d.dynamic dInstance = Activator.CreateInstance(type); //e.dInstance.Query(); //f.dInstance.Get(); //报错了--因为SqlServerHelper没有Get方法
类型转化:
//4、类型转换 // SqlServerHelper helper = (SqlServerHelper)oInstance; //不建议这样转换--如果真实类型不一致--会报报错; IDBHelper helper = oInstance as IDBHelper;//如果类型一直,就转换,如果不一致;就返回null
调用方法:
//5、调用方法 helper.Query();
不过本文并没有用到反射的一些功能,只是用到了反射相关的Type类。
接口实现
撸代码之前我们先分析一下具体要实现什么效果,既然是操作数据库的类,自然要实现基本的增删改查功能,而ORM可以根据数据库生成实体类或者根据实体类生成数据库,仔细想想的话似乎根据实体类生成数据库跟容易些,那接口大致就是这样:
(先声明一下,本人也是个C#小白,方法命名和封装都不是很规范,代码也只是提供个思路,很多功能实现的不完整,请大佬门嘴下留情,友好交流。)
interface IBaseHelper<T> where T : BaseId { List<T> Query(); List<T> Query(int id); List<T> Query(Func<T,bool> func); bool Edit(T t); bool CreateRow(T t); bool CreateTable(); bool Delete(int id); }
BaseId类:
public class BaseId { public BaseId(int id) { Id = id; } public BaseId(){ } public int Id { get; set; } }
MySqlHelper类实现
因为本人用的MySql数据库,所以写的是MySql的Helper类,用其他数据库的可以尝试根据我的修改为其他数据库,核心实现都写好了注释,改写应该不会太难:
public class BaseMySqlHelper<T> : IBaseHelper<T> where T : BaseId, new() { /// <summary> /// 数据库查询方法 /// </summary> /// <param name="sql">sql语句</param> /// <returns>查询结果</returns> public List<T> SqlQuery(string sql) { List<T> listT = new List<T>(); MySqlConnectionStringBuilder sBuilder = new MySqlConnectionStringBuilder(); sBuilder.Server = "localhost"; sBuilder.UserID = "root"; sBuilder.Password = "000124lrpLRP"; sBuilder.Database = "lrp"; using (MySqlConnection conn = new MySqlConnection(sBuilder.ConnectionString)) { conn.Open(); MySqlCommand mySqlCommand = new MySqlCommand(sql, conn); var result = mySqlCommand.ExecuteReader(); while (result.Read()) { T t = new T(); foreach (PropertyInfo property in typeof(T).GetProperties()) { //由于数据库的空值类型与C#的空值类型不同,所以需要先做个判断 property.SetValue(t, DBNull.Value.Equals(result[property.Name]) ? null : result[property.Name]); } listT.Add(t); } } return listT; } /// <summary> /// 将C#的类型转换为MySql的类型,用于建表 /// </summary> /// <param name="propertyInfo">传入一个数据类型</param> /// <returns>转换后的Sql类型</returns> public string PropertySwap(PropertyInfo propertyInfo) { switch (propertyInfo.PropertyType.Name) { case "Int32":return "int"; case "String":return "varchar(100)"; case "Double":return "float"; case "Decimal":return "decimal"; case "DateTime":return "datetime"; default:return ""; } } /// <summary> /// 修改数据库方法 /// </summary> /// <param name="sql">sql语句</param> /// <returns>true为执行成功,false为失败</returns> public bool SqlSet(string sql) { int result = 0; MySqlConnectionStringBuilder sBuilder = new MySqlConnectionStringBuilder(); sBuilder.Server = "localhost"; sBuilder.UserID = "root"; sBuilder.Password = "000124lrpLRP"; sBuilder.Database = "lrp"; using (MySqlConnection conn = new MySqlConnection(sBuilder.ConnectionString)) { conn.Open(); MySqlCommand mySqlCommand = new MySqlCommand(sql, conn); result = mySqlCommand.ExecuteNonQuery(); } return result!=-1; } public List<T> Query() { string sql = $"select * from {typeof(T).Name}"; return SqlQuery(sql); } public List<T> Query(int id) { string sql = $"select * from {typeof(T).Name}"; return SqlQuery(sql).Where<T>(t=> t.Id == id ).ToList(); } public List<T> Query(Func<T,bool> func) { string sql = $"select * from {typeof(T).Name}"; return SqlQuery(sql).Where<T>(func).ToList(); } public bool Edit(T t) { string sql = $"update {typeof(T).Name} set "; Type type = typeof(T); List<string> sqlData = new List<string>(); foreach (PropertyInfo property in type.GetProperties()) { if (property.PropertyType.Name == "String") sqlData.Add($@"{property.Name}='{property.GetValue(t)}'"); else sqlData.Add($"{property.Name}={property.GetValue(t)}"); } sql += string.Join(',', sqlData); sql += " where id=" + t.Id; return SqlSet(sql); } public bool CreateRow(T t) { string sql = $"insert into {typeof(T).Name} set "; Type type = typeof(T); List<string> sqlData = new List<string>(); foreach (PropertyInfo property in type.GetProperties()) { if(property.PropertyType.Name=="String")sqlData.Add($@"{property.Name}='{property.GetValue(t)}'"); else sqlData.Add($"{property.Name}={property.GetValue(t)}"); } sql+=string.Join(',', sqlData); return SqlSet(sql); } public bool Delete(int id) { string sql = $"delete from {typeof(T).Name} where id="+id; return SqlSet(sql); } public bool CreateTable() { string sql = $"CREATE TABLE IF NOT EXISTS {typeof(T).Name} ( "; List<string> sqlData = new List<string>(); foreach (PropertyInfo property in typeof(T).GetProperties()) { sqlData.Add($"{property.Name} {PropertySwap(property)}"); } sql += string.Join(',', sqlData); sql += ")"; return SqlSet(sql); } }
难点在于从泛型实例上读取属性值和修改泛型类型实例的属性值,使用了property.SetValve()和property.GetValue(),因为直接用实例.属性会无法通过编译,且无法实现读取不同类型的不同属性。
总结
最后可以创建一个App.config存储数据库的连接语句,实现可配置。不过这次只是写了一个小Demo,真正的ORM框架需要用更多更加严谨的代码来完成,比如考虑效率问题、可创建主键类型与可空类型值、异步执行等等。不过如果你还不太理解ORM,看完我的代码相信对你理解ORM会有很大帮助。