一、应用场景
假设我们有一张数据表Student,并且有以下字段
public class Student { public int Id { get; set; } public string Name { get; set; } public int Grade{ get; set; } }
当我们在写数据访问层的时候,需要根据Id获取数据,那我们就很自然的写一个方法 GetStudentById(int Id);假設需求还需要根据Name来获取数据,我们也很自然的再写另一个方法GetStudentByName(string name);此时,项目中,还需要一个根据年纪来查询数据的方法,或者根据Name和Grade来查询数据的方法,我们也会新增一个数据访问层方法或者进行方法重载GetStudent(string name,int Grade);
而在我们写的这些方法中,几乎99%的代码是相同的,唯一不同的仅仅是Sql语句后的where条件而已,所以,我今天就想,有没有一种方法,我传入一个Student对象,方法自己判断我需要查询哪些字段呢?当然有,那就是反射了,不过,事后我发现还是有一点点缺陷,文章末尾会说到!
二、"撸"起袖子,打开Visual Studio
Demo开始前,你需要具备一点点的反射基础:
1、using System.Reflection; 绝大部分使用的方法在这个命名空间中,有兴趣的可以使用Reflector反编译研究下源码
2、Type t = object.GetType(); 获取当前实例的类型描述信息,从而描述信息当中,我们就可以很容易知道当前实例有哪些方法、属性等等……
3、 PropertyInfo[] infos = t.GetProperties(); 获取从当前实例得到的描述信息中得到相关的属性数组,也就是这里可以得到Id,Name,Grade;
三、Demo思路
数据访问层的查询方法,接受一个Student实例参数,在调用该方法前,我们在业务逻辑层中实例化一个Student对象,并根据查询条件对该实例的属性进行赋值,比如,我想根据Id等于1的学生信息,那就在实例化对象后,对Id进行赋值即可。
具体思路:
(1)利用反射,获取条件参数实例中的属性数组;
(2)遍历该属性数组,对当前属性的值进行判断,如果为空,则不在查询条件中,如果不为空,则是业务逻辑层想要的查询条件;
(3)接上步,如果不为空,则获取当前属性的名称、Value,进行条件的拼接,装载在集合中;
(4)遍历完毕,对集合进行join拼接;
private Student GetStudent(Student stu) { //获取当前实例的描述信息 Type t = stu.GetType(); //从描述信息当中,获取当前实例的属性数组 PropertyInfo[] infos = t.GetProperties(); //用于装载最终的查询条件的集合 List<string> list = new List<string>(); for (int i = 0; i < infos.Length; i++) { //当前属性的值不为null,则证明是业务逻辑层中想要的查询条件 if (infos[i].GetValue(stu, null) != null) { string temp = string.Format("{0}='{1}'", infos[i].Name, infos[i].GetValue(stu, null)); list.Add(temp); } } //拼接sql语句 string s = string.Join("and", list); //todo:拼接SQL语句,进行数据库查询 return null; }
四、验证
假如,我想要查询Id=1并且Name=dotnetgeek的条件的学生记录,那我们就要在调用数据访问层方法前,进行初始对象实例化,并且对属性进行赋值;
Student s = new Student(); s.Id = 1; s.Name = "dotnetgeek"; //调用数据访问层方法 GetStudent(s);
则最终拼接出来的SQL条件则会是如图所示:(这里只演示到拼接SQL语句的步骤,进行数据查询不是本文章讨论范围)
五、Bug
不知道大家有没有发现到,查询条件里面出现了一个Bug,请留意我调用方法前是对Id,Name属性进行赋值的,最终SQL条件里又多了一个Grade属性,这里因为实体类中,Grade是int类型,是int类型的话就会默认值为0,所以,如果想用这种解决方法的话,还需在判断为null的地方再判断不能为0,but,如果用户想查询该字段等于0的记录怎么办?
六、总结
这个Demo是我在编码中浮想出来的,当时我已经写了2个功能相同的方法,加上同事已经写过1个相类似的方法,整个数据访问层中已经出现了相似的三个方法了,假如其他同事又有别的业务需求,是不是要再写第4个,第5个,第……个呢?所以我就想有没有一种方法可以实现一下,再说,在Entity Framework中,进行删除操作也是传入一个实体模型的,如果有兴趣的可以去研究下EF的源码!