使用泛型和反射实现一个简单的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");
View Code

  获取类型:

//2、获取某一个具体的类型,参数需要是类的全名称
Type type1 = assembly.GetType("Business.DB.SqlServer.SqlServerHelper");
View Code

  创建对象:

//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方法
View Code

  类型转化:

//4、类型转换
// SqlServerHelper helper = (SqlServerHelper)oInstance; //不建议这样转换--如果真实类型不一致--会报报错; 
IDBHelper helper = oInstance as IDBHelper;//如果类型一直,就转换,如果不一致;就返回null
View Code

  调用方法:

//5、调用方法
helper.Query();
View Code

不过本文并没有用到反射的一些功能,只是用到了反射相关的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);
    }
View Code

BaseId类:

public class BaseId
    {
        public BaseId(int id)
        {
            Id = id;
        }
        public BaseId(){ }

        public int Id { get; set; }

    }
View Code

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

  难点在于从泛型实例上读取属性值和修改泛型类型实例的属性值,使用了property.SetValve()和property.GetValue(),因为直接用实例.属性会无法通过编译,且无法实现读取不同类型的不同属性。

总结

  最后可以创建一个App.config存储数据库的连接语句,实现可配置。不过这次只是写了一个小Demo,真正的ORM框架需要用更多更加严谨的代码来完成,比如考虑效率问题、可创建主键类型与可空类型值、异步执行等等。不过如果你还不太理解ORM,看完我的代码相信对你理解ORM会有很大帮助。

posted @ 2022-10-29 16:26  lrplrplrp  阅读(68)  评论(0编辑  收藏  举报