利用泛型和反射.递归将数据集中的数据添加到集合中
现在数据库一张表student,其中有属性若干,映射到C#代码中有Student_Model类,sql语句:"select * from student"(仅做测试).
1 public class Student_Model 2 { 3 public int StudentNo { get; set; } 4 public string LoginPwd { get; set; } 5 public string StudentName { get; set; } 6 public bool Sex { get; set; } 7 public int GradeID { get; set; } 8 public string Phone { get; set; } 9 public string Address { get; set; } 10 public DateTime BornDate { get; set; } 11 public string Email { get; set; } 12 public string IdentityCard { get; set; } 13 }
然后通过ADO.Net读取数据,将读取到的数据存入DataSet中,这个时候,问题来了,我们传统的方式是用循环逐行添加,比如:
1 List<Student_Model> students = ...; //这里获得Student_Model List<T>对象 2 DataSet ds =...;//这里获得ds对象,已存储完数据 3 foreach(DataRow row in ds.Tables[0].Rows) 4 { 5 students.Add( 6 new Student_Model{ 7 StudentNo = item["studentno"].ToInt32(), 8 LoginPwd = item["LoginPwd"].ToString(), 9 StudentName = item["StudentName"].ToString(), 10 Sex = item["Sex"].ToBoolean(), 11 GradeID = item["GradeID"].ToInt32(), 12 Phone = item["Phone"].ToString(), 13 Address = item["Address"].ToString(), 14 BornDate = item["BornDate"].ToDateTime(), 15 Email = item["Email"].ToString(), 16 IdentityCard = item["IdentityCard"].ToString(), 17 } 18 ); 19 }
其中ToInt32()等方法,均在其他类中重写,最后将会补上重写代码;
从上面的遗传代码可以看出,数据可以成功添加进集合中,但是,当我们利用抽象工厂调用不同数据库的时候的时候,同一个方法要写几遍,又或者,你需要编写很多个查询方法,并依次将ds对象插入数据集中,又或者,你这个对象中有100个属性,甚至是1000个...
这个时候,我们需要封装和复用代码...
但你说,我能用复制粘贴,好吧,那么你可以关闭这个网页了...
抽取这个方法不失为一个好方法,具体怎么做呢?
将要复用的代码抽取到一个方法中,比如,上一个代码块中的整个foreach循环,很明显,这个将是我们要大量复用的.
简单的抽取肯定不行,这时候我们需要用到泛型,反射,和递归来完成一个无可匹敌的公共方法,这个方法可查询所有对象,包括对象中包含的自定义对象,比如,Student_Model包含一个年级类Grade_Model.
步骤如下:
创建一个泛型类,如DataSetToList<T>:
1 public class DataSetToList<T> 2 { 3 4 }
在这个类中添加方法:
public List<T> GetList(DataSet ds, int TableIndex) { List<T> lists = new List<T>(); foreach (DataRow item in ds.Tables[TableIndex].Rows) { //代码 } return lists; }
我们将要在这里面完成封装和复用,将代码修改如下:
1 public List<T> GetList(DataSet ds, int TableIndex) 2 { 3 List<T> lists = new List<T>(); 4 foreach (DataRow item in ds.Tables[TableIndex].Rows) 5 { 6 T t = Activator.CreateInstance<T>();//创建泛型类的实例 7 foreach(PropertyInfo p in t.GetType().GetProperties()){ 8 p.SetValue(t,item[p.Name],null); 9 } 10 lists.Add(t); 11 } 12 return lists; 13 }
我们分析一下以上代码,首先创建了一个集合,这个集合最后要作为结果返回;然后循环参数DataSet的ds对象的行;
其中我们要注意的是,如何创建泛型对象,T t = Activator.CreateInstance<T>();
关于Activator类,MSDN中的解释是:包含特定的方法,用以在本地或远程创建对象模型,此类不能被继承.该类的签名是public class seald Activator:_Activator ;命名空间是System;
关于Activator.CreateInstance<T>()方法,MSDN的描述是:使用无参构造函数,创建指定泛型类型参数所指定类型的实例. 这句话听起来有点拗口,其实就是创建当前泛型类的对象,创建方式是无参构造函数. 该方法的签名是public static T CreateInstance<T>();
好了,到这里为止,我们创建了对象,但是还没有对对象赋值,此时就应该遍历泛型类的所有公共属性;
泛型类的公共属性由泛型类的对象t的GetType()方法来获得t的当前实例的Type,Type在MSDN中的描述是:表示类型声明:类类型,接口类型,数组类型,值类型,枚举类型,类型参数,泛型类型定义,以及开放或封闭构造的泛型类型. 该Type类的签名是public abstract class Type : MemberInfo, _Type, IReflect
然后再调用该类型的GetProperties()方法来获得所有公共属性,返回一个数组;p.SetValue(参数1:其属性值的对象, 参数2:新的属性值, 参数3:索引化属性的可选索引值).该方法签名是:public virtual void SetValue(Object obj,Object value,Object[] index);
至此,我们算是完成一个简单的,比如"select * from student"的语句查询,没有问题,但是,一旦我们将Student_Model类稍作修改呢?
1 public int StudentNo { get; set; } 2 public string LoginPwd { get; set; } 3 public string StudentName { get; set; } 4 public bool Sex { get; set; } 5 public int GradeID { get; set; } 6 public string Phone { get; set; } 7 public string Address { get; set; } 8 public DateTime BornDate { get; set; } 9 public string Email { get; set; } 10 public string IdentityCard { get; set; } 11 //年级 12 public Grade_Model Grade { get; set; } 13 }
我们添加了第十二行,为此,添加一个Grade_Model的类:
1 public class Grade_Model 2 { 3 public int GradeID { get; set; } //年级编号 4 public string GradeName { get; set; } //年级名称 5 }
那么同样是上面那个所谓的通用方法,到这里,是否还会适用么?
结果是System.ArgumentException:列"Grade"不属于表Table异常;所以我们还要稍作修改:
1 public class DataSetToList<T> 2 { 3 public List<T> GetList(DataSet ds, int TableIndex) 4 { 5 List<T> lists = new List<T>(); 6 foreach (DataRow item in ds.Tables[TableIndex].Rows) 7 { 8 T t = Activator.CreateInstance<T>();//创建泛型类的实例 9 foreach(PropertyInfo p in t.GetType().GetProperties()){ 10 //判断是否系统类型 11 if (p.PropertyType.ToString().StartsWith("System.")) 12 { 13 p.SetValue(t, item[p.Name], null); 14 } 15 else 16 { 17 //如果不是,则... 18 } 19 } 20 lists.Add(t); 21 } 22 return lists; 23 } 24 }
我们添加了一行if判断,当他是系统类型的时候正常赋值,如果不是,则需要写新的代码;
属性PropertyInfo的类型是用其属性PropertyType来获取,而不是GetType(),GetType()是获取当前属性的对象实例的Type;
显然,如果不是系统类型那么肯定是自定义的,如Grade_Model,在这里,我们要考虑到Grade_Model类中是否还有第三个类,比如Subject_Model课程信息类;
1 public List<T> GetList(DataSet ds, int TableIndex) 2 { 3 List<T> lists = new List<T>(); 4 foreach (DataRow item in ds.Tables[TableIndex].Rows) 5 { 6 T t = Activator.CreateInstance<T>();//创建泛型类的实例 7 GetT(item,t); 8 lists.Add(t); 9 } 10 return lists; 11 } 12 13 public void GetT(DataRow row,Object obj) 14 { 15 foreach (PropertyInfo p in obj.GetType().GetProperties()) 16 { 17 if (p.PropertyType.ToString().StartsWith("System.")) 18 { 19 if(row.Table.Columns.Contains(p.Name)){ 20 p.SetValue(obj,row[p.Name],null); 21 } 22 } 23 else 24 { 25 Object o = p.PropertyType.Assembly.CreateInstance(p.PropertyType.ToString()); 26 GetT(row, o); 27 p.SetValue(obj,o,null); 28 } 29 } 30 }
至此,这个代码才算完整,基本能应对各种查询带来的ds转化,当然,代码要注意实体类主外键映射;
我们分析一下以上写的代码:
我们抽取了一个GetT()的方法,参数是DataSet类型和Object类型;
第19行,row.Table.Columns.Contains(p.Name),判断数据库列是否包含属性名称,如果是,则添加数据,否则返回继续;
第23行开始, 是当该公共属性是一个自定义类型的时候,我们重新创建一个Object,该Object由属性的对象,先获得PropertyType属性,然后获得该属性类型所在的程序集,并创建该属性类型的对象,所以参数是p.PropertyType.ToString();
最后,提下重写ToInt32()方法:
1 public static class ToolHelper 2 { 3 #region Convert Method 4 public static Int32 ToInt32(this Object obj) 5 { 6 return Convert.ToInt32(obj); 7 } 8 public static Int16 ToInt16(this Object obj) 9 { 10 return Convert.ToInt16(obj); 11 } 12 public static Int64 ToInt64(this Object obj) 13 { 14 return Convert.ToInt64(obj); 15 } 16 public static bool ToBoolean(this Object obj) 17 { 18 return Convert.ToBoolean(obj); 19 } 20 public static DateTime ToDateTime(this Object obj) 21 { 22 return Convert.ToDateTime(obj); 23 } 24 public static double ToDouble(this Object obj) 25 { 26 return Convert.ToDouble(obj); 27 } 28 #endregion 29 }
到这里,该知识点就基本介绍完了...