继SqlHelper之后,从D层到B层甚至U层传递的都是实体和DataTable,在以前写的一篇博客《传参数与传实体、返回实体与返表——三层架构登录实例》中也比较过二者的优缺点,只是最近又发现它们两个的不完美实在是太严重了。
DataTable
先说DataTable吧,在D层从数据库查询到的结果都是布尔值和DataTable,然后一个return就可以返到B、U层,但是在用数据的时候就犯难了。按理论来说,U、D两层间各自的结构是互不清楚的,但是要想在U层使用DataTable中的某个数据就必须要知道这个数据对应的是数据库表中的哪个字段或者D层给它定义成了哪个字段,例如:
查询学生信息的时候,我想知道学生的性别是什么,这个时候我写成"DataTable(0).性别"、"DataTable(0).1"、"DataTable(0).2……还是"DataTable(0).Sex"?显然在我不知道D层或数据库中的结构是确定不了哪种形式的取值是对的。
DataTable造成了层与层之间严重的耦合,对数据库也是非常危险的,所以此种做法还是避而远之吧。
实体
再看实体,来一段没加SqlHelper之前的代码(加了SqlHelper之后,为了方便基本上都用成了DataTable,所以将就着看下吧)
Public Function SelectStudent(ByVal student As E_StudentInfo_View) As E_StudentInfo_View Dim conn As New SqlConnection '创建连接对象 Dim cmd As New SqlCommand '创建命令对象 conn = New SqlConnection(ConnStr.Connectstring()) '创建数据库连接 cmd.Connection = conn '查询数据库 cmd.CommandText = "Select * From StudentInfo Where StudentNo=@StudentNo" cmd.Parameters.Add(New SqlParameter("@StudentNo", student.StudentNo)) cmd.CommandType = CommandType.Text '获取SQL语句的具体类型,在这里为Select conn.Open() Dim reader As SqlDataReader = cmd.ExecuteReader '执行查询语句,并生成一个DataReader Dim nstudent As New E_StudentInfo_View '实例化新的UserInfo,用于保存返回的实体 '获取查询到的数据,并返回给相应的属性 While reader.Read() '判断用户信息是否为空,为什么需要判断? If nstudent Is Nothing Then nstudent = New E_StudentInfo_View End If nstudent.StudentNo = reader.GetInt32(0) nstudent.StudentName = reader.GetString(1) nstudent.Sex = reader.GetString(2) nstudent.Department = reader.GetString(3) nstudent.Grade = reader.GetString(4) nstudent.eClass = reader.GetString(5) nstudent.Descr = reader.GetString(6) End While Return nstudent '返回得到的实体 conn.Close() '关闭数据库连接 End Function
程序中清楚的体现了我是怎么将查询到的数据填充实体——一条条逐个赋值,更有意思的是在界面层显示的时候再将实体的属性一条条赋给控件。如果某个实体有十几二十个甚至上百个属性,那……累死我也没人偿命的,何况还有返回多个实体的情况。
泛型
那么,有什么办法既可以不破坏耦合又不用一条条的将DataTable填充给实体的属性呢?这就是我为什么重学数组、集合的原因,通过泛型集合可以很好的解决这个问题,即将DataTable转化成List(ofT),T就是E层中的各个实体,所以这里最重要的就是提取一个方法来完成这个转换。我把它放在了E层,单独成类ConvertHelper
Imports System.Collections.Generic '添加泛型集合的命名空间 Imports System.Reflection '添加反射的命名空间,以便使用PropertyInfo Public Class ConvertHelper '将DataTable转化为泛型集合 Public Shared Function ConvertToList(Of T As New)(ByVal dt As DataTable) As IList(Of T) Dim myList As List(Of T) = New List(Of T)() '定义返回的集合 Dim myType As Type = GetType(T) '得到实体类的类型名 Dim tempName As String = String.Empty '定义一个临时变量 Dim dr As DataRow '定义行集 '遍历DataTable的所有数据行 For Each dr In dt.Rows Dim myT As New T '创建一个实体类的对象 Dim arrayPi As PropertyInfo() = myT.GetType().GetProperties() '定义属性集合 Dim pi As PropertyInfo '遍历pi对象的所有属性 For Each pi In arrayPi tempName = pi.Name '将属性名称赋值给临时变量 '检查DataTable是否包含此列(列名==对象的属性名) If (dt.Columns.Contains(tempName)) Then '将此属性与DataTable里的列名比较,查看DataTable是否包含此属性 '判断此属性是否有Setter If (pi.CanWrite = False) Then '判断此属性是否可写,如果不可写,跳出本次循环 Continue For End If Dim value As Object = dr(tempName) '定义一个对象型的变量来保存列的值 '如果非空,则赋给对象的属性 If (value.ToString() <> DBNull.Value.ToString()) Then pi.SetValue(myT, value, Nothing) '在运行期间,通过反射,动态的访问一个对象的属性 End If End If Next myList.Add(myT) '添加到集合 Next Return myList '返回实体集合 End Function End Class
使用时调用方法即可
Public Function SelectUserInfo(ByVal user As E_UserInfo) As IList Dim mySqlHelper As New SqlHelper '创建一个SqlHelper Dim dt As New DataTable '定义一个DataTable数据表 Dim cmdText As String = "Select * from UserInfo Where Level=@Level" Dim paras As SqlParameter() = {New SqlParameter("@Level", user.Level)} '将参数传入SqlHelper dt = mySqlHelper.ExecSelect(cmdText, CommandType.Text, paras) '执行查询 Dim myList As New List(Of E_UserInfo) '定义一个集合用来保存转化后的泛型集合 myList = ConvertHelper.ConvertToList(Of E_UserInfo)(dt) '调用实体转换类,将DataTable转换为泛型集合 Return myList '返回结果集 End Function
在界面层可以直接将List(ofT)绑定到DataGridView做数据源,也可以像数组一样随意提取实体的属性,使用非常灵活,可以说它集DataTable和实体中我们需要用的优点于一身了
小结:
机房收费系统中有很多类似ConvertToList这样的转换思想,在一条路走不顺的时候不妨试着换一条路,失败了收获的是经验,成功了又多一种方法。