自研ORM(二)单表查询升级到支持多种表查询
本分类以最简单的ADO.NET为基础,通过一步步升级完善,形成自研的ORM框架。针对每小章所运用的技术,会在文章开头有大概描述。
平台环境: VS2022 基于.NET 6
本章目标:由单表查询升级到支持多个不同表的查询,动态生成SQL语句,动态赋值。
本章使用到的技术点: ADO.NET 、泛型、反射、Linq
笔者首先写了一个根据ID查询固定表的方法
/// <summary> /// 根据主键查询 /// 目前此方法只针对一个实体(表),无法做到通用 /// </summary> /// <param name="id"></param> public Company GetCompany(int id) { Company company = new Company(); using SqlConnection connection = new SqlConnection(connectionString); connection.Open(); string sql = @"SELECT [Id] ,[Name] ,[CreatorId] ,[LastModifierId] ,[CreateTime] ,[LastModifyTime] FROM[AdvancedCustomerDB].[dbo].[Company] where id=" + id; SqlCommand cmd = connection.CreateCommand(); cmd.CommandText = sql; SqlDataReader dr = cmd.ExecuteReader(); if (dr.Read()) { company.Id= Convert.ToInt32( dr["id"]); company.Name = Convert.ToString(dr["Name"]); company.CreatorId = Convert.ToInt32(dr["CreatorId"]); company.LastModifierId = Convert.ToInt32(dr["LastModifierId"]); company.LastModifyTime = Convert.ToDateTime(dr["LastModifyTime"]); company.CreateTime = Convert.ToDateTime(dr["CreateTime"]); } return company; }
在完成上述代码后,带着如下几个疑问进行思考
1.如和做到一个方法满足多种诉求?
这里 我们首先想到的肯定是泛型
2.如果做到一个方法满足多种诉求后,我们调用方法怎么完成不同实体(意味着不同的表)的数据绑定
需要用到反射,反射可以获取对象、属性以及给属性赋值
3.如何实现SQL语句的编写(不同的表)
实体类和数据库的表是完全一致,获取实体类的属性就相当于获取表的字段
获取的对象就是数据库的表名称
动态生成SQL语句
/// <summary> /// 升级版(1) /// 支持不同表的查询 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="id"></param> /// <returns></returns> public T FindByID<T>(int id) where T:new ()//泛型约束,必须是无参构造方法 { Type type = typeof(T); object? result= Activator.CreateInstance(type);//反射创建实例,调用的是无参构造函数,这里需要type类型 using SqlConnection connection = new SqlConnection(connectionString); connection.Open(); //思考: 既然是针对多个不同的表,那写死的SQL语句肯定无法满足需求 #region 固定SQL语句(只支持单表) //string sql = @"SELECT [Id] // ,[Name] // ,[CreatorId] // ,[LastModifierId] // ,[CreateTime] // ,[LastModifyTime] // FROM[AdvancedCustomerDB].[dbo].[Company] where id=" + id; #endregion //SQL语句也应该依赖T类,动态生成 //实体类和数据库的表是完全一致,获取实体类的属性就相当于获取表的字段 //获取的对象就是数据库的表名称 //动态生成SQL语句 string sql = $"select {string.Join(",", type.GetProperties().Select(c => c.Name).ToList())} from {type.Name} where id=" + id; SqlCommand cmd = connection.CreateCommand(); cmd.CommandText = sql; SqlDataReader dr = cmd.ExecuteReader(); if (dr.Read()) { //思考:这里其实就是属性值的绑定,如何做到通用呢? //需要利用反射来实现 //反射如何给对象的属性赋值?1.获取对象 2获取属性 3给属性赋值 #region 原有针对单个表完成数据绑定 { //company.Id = Convert.ToInt32(dr["id"]); //company.Name = Convert.ToString(dr["Name"]); //company.CreatorId = Convert.ToInt32(dr["CreatorId"]); //company.LastModifierId = Convert.ToInt32(dr["LastModifierId"]); //company.LastModifyTime = Convert.ToDateTime(dr["LastModifyTime"]); //company.CreateTime = Convert.ToDateTime(dr["CreateTime"]); } #endregion { //循环遍历属性,逐个给属性赋值 foreach (var item in type.GetProperties()) { //思考:不同的表,属性肯定存在差异,如果每个属性都这么判断赋值,那肯定无法做到通用 //观察如下代码,可以发现,每次循环必然只进入一个判断,且都是取出dr[item.name] //if (item.Name.Equals("id")) //{ // item.SetValue(result, dr[item.Name]); //} //else if (item.Name.Equals("Name")) //{ // item.SetValue(result, dr[item.Name]); //} //........................ //那我们可以不用判断,直接取item.name //如果数据库表中的某一个字段为null,reader[prop.Name]获取到的名称为DBNull----在C#程序中的代表的是null; 但是在C#程序中,null 不能 直接使用 DBnull来替换 item.SetValue(result, dr[item.Name] is DBNull ? null: dr[item.Name]); } } } return (T)result; }