利用泛型和反射.递归将数据集中的数据添加到集合中

现在数据库一张表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 }

到这里,该知识点就基本介绍完了...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2013-06-05 00:13  Mr_Zack  阅读(561)  评论(0编辑  收藏  举报