引言
在机房重构还剩下上下机功能的时候突然听到这么一个消息——我们最好不要用DataTable来从D层返回,应该用泛型集合!当时没有特别在意这件事情,因为那时候的注意力都在设计模式的添加和应用上面,没有过多的考虑“泛型”这方面的知识,当我完成基本功能以后,这个想法有冒出来了,所以这次没有再次放过,而是找了一些相关资料来总结了一下,下面和大家共享一下我的总结:
DataTabel的弊端
首先我们来看一下我们使用DataTable时候的U层的代码:
'填充文本框
txtStudentNo.Text = dt.Rows(0).Item(2)
txtName.Text = dt.Rows(0).Item(3)
txtSex.Text = dt.Rows(0).Item(4)
txtTie.Text = dt.Rows(0).Item(5)
txtGrade.Text = dt.Rows(0).Item(6)
txtClass.Text = dt.Rows(0).Item(7)
txtStatus.Text = dt.Rows(0).Item(11)
txtExplain.Text = dt.Rows(0).Item(9)
txtBalance.Text = dt.Rows(0).Item(8)
这是当我们从数据库中查到结果以后显示在我们窗体上的代码,从上面的代码我们可以看出,我们必须清楚的知道数据库中字段名对应的位置,这样就和数据库的耦合性太大,这在我们编程中是特别忌讳的,另外它不符合面向对象的思想而更倾向于面向过程,所以我们仔细想想DataTable是非常不可取的一个东西,当我们数据库表中的字段非常多的时候,会给我们带来难以调试的错误。
什么是泛型集合?
泛型听起来很高深的一个词,但实际上它的作用很简单,就是提高程序的性能.比如在计算机中经常用到一些数据结构,如队列,链表等,而其中的元素以前一般这么定义:object a=new object();这样就带来一个严重的问题,用object来表示元素没有逻辑问题,但每次拆箱、封箱就占用了大量的计算机资源,导致程序性能低下,而这部分内容恰恰一般都是程序的核心部分,如果使用object,那么程序的表现就比较糟糕.
而使用泛型则很好的解决这个问题,本质就是在编译阶段就告诉编译器,数据结构中元素的种类,既然编译器知道了元素的种类,自然就避免了拆箱、封箱的操作,从而显著提高c#程序的性能.比如List就直接使用string对象作为List的元素,而避免使用object对象带来的封箱、拆箱操作,从而提高程序性能.
泛型集合和DataTable的区别
在三层架构中,实体类即数据库的映射,因此实体类中的属性和数据库表中的字段是相对应的。把DataTable中的每一行记录视为一个实体类,把其中的字段读取出来,到实体类的属性中,再把所有的实体类存在泛型集合中。因此,DataTable中有多少个记录,泛型集合中就有多少个实体类,每个实体类的属性和DataTable的字段是相对应的。这样一来,传到B层或U层的将是一个实体类的泛型集合。
使用泛型集合传递数据,编写B层的人员无需手动填写需要的字段,直接按一下点,全都提示出来了,想用哪个用哪个,不会出现写错的情况;你不必了解数据库结构;符合面向对象思想。
泛型集合的使用
下面以查询学生余额功能来展示泛型集合的使用,因为我们在机房收费系统的时候有多个查询功能,而我们通过Sqlhelper类返回的都是DataTable,所以我们需要多次进行泛型集合的转换,为了体现代码的复用,我们将这个转换的方法抽象出单独的一个类放在D层,以供调用。
'**********************************************
'说明:将DataTable转化为泛型集合
'命名空间:DAL
'机器名称:晓
'创建日期:2015/2/23 11:13:28
'作者:郑浩
'版本号:V1.00
'**********************************************
Imports System.Collections.Generic '增加泛型的命名空间
Imports System.Reflection '引入反射:为了使用PropertyInfo
Public Class ConvertGenericsHelper
'将datatable转化为泛型集合
Public Shared Function convertToGenerics(Of T As {New})(ByVal dt As DataTable) As IList(Of T)
'注意: 这里的new是用来约束T的,必须有,不然new T的时候会出现错误
Dim myGenericsAs New List(Of T) '定义最终返回的集合
Dim myTpye As Type = GetType(T) '得到实体类的类型名
Dim dr As DataRow '定义行集
Dim tempName As String = String.Empty '定义一个临时变量
'遍历DataTable的所有数据行
For Each dr In dt.Rows
Dim myT As New T '定义一个实体类的对象
Dim propertys() As PropertyInfo = myT.GetType().GetProperties() '定义属性集合
Dim Pr As PropertyInfo
'遍历该对象的所有属性
For Each Pr In propertys
tempName = Pr.Name '将属性名称赋值给临时变量
'检查DataTable是否包含此列(列名==对象的属性名)
If (dt.Columns.Contains(tempName)) Then '将此属性与datatable里的列明比较,查看datatable是否包含此属性
'判断此属性是否有Setter
If (Pr.CanWrite = False) Then '判断此属性是否可写,如果不可写,跳出本次循环
Continue For
End If
Dim value As Object = dr(tempName) '定义一个对象型的变量来保存列的值
If (value.ToString <> DBNull.Value.ToString()) Then '如果非空,则赋给对象的属性
Pr.SetValue(myT, value, Nothing) '在运行期间,通过反射,动态的访问一个对象的属性
End If
End If
Next
myGenerics.Add(myT) '添加到集合
Next
Return myGenerics '返回实体集合
End Function
End Class
D层代码:
'**********************************************
'说明:利用泛型集合来查看学生余额
'命名空间:DAL
'机器名称:晓
'创建日期:2015/2/23 11:27:21
'作者:郑浩
'版本号:V1.00
'**********************************************
Imports IDAL
Imports System.Data.SqlClient
Public Class SqlserverCheckBalanceDAL : Implements ICheckBalance
Public Function ICheckBalance(studentinfo As Entity.EN_StudentInfo) As List(Of Entity.EN_StudentInfo) Implements ICheckBalance.ICheckBalance
Dim sql As String
sql = "select * from ZH_StudentInfo where CardNo = @CardNo"
Dim paras As SqlParameter() = {New SqlParameter("CardNo", studentinfo.CardNo)}
Dim dt As New DataTable
'保存转换后的泛型集合
Dim myList As New List(Of Entity.EN_StudentInfo)
dt = SqlHelper.SqlHelper.ExecSelect(sql, CommandType.Text, paras)
'先判断dt是否为空
If dt.Rows.Count > 0 Then
'将dt转换为泛型集合
myList = ConvertGenericsHelper.convertToList(Of Entity.EN_StudentInfo)(dt)
Return myList
Else
Return Nothing
End If
End Function
End Class
U层核心部分代码:
Dim studentinfo As New Entity.EN_StudentInfo
studentinfo.CardNo = Trim(txtCardNo.Text)
Dim dt As New List(Of Entity.EN_StudentInfo)
Dim checkbalance As New Facade.FacadeCheckBalance
dt = checkbalance.CheckBalance(studentinfo)
txtClass.Text = dt(0).Classes '0表示泛型集合中的第一个实体
txtStudentNo.Text = dt(0).StudentNo
txtName.Text = dt(0).StudentName
txtSex.Text = dt(0).Sex
txtStatus.Text = dt(0).Status
txtGrade.Text = dt(0).Grade
txtBalance.Text = dt(0).Cash
txtExplain.Text = dt(0).Explain
上面是我们直接给文本框赋值,和DataGridView控件交互和DataTable的用法一样,直接将我们声明的泛型集合赋值给DataGridView的DataSourc即可。
常见错误
上面这中错误是我们在运用泛型的时候经常遇到的错误,原因是我们封装的实体中的属性的类型和数据库中相应字段的类型不同,例如上面错误的原因是在数据库中cash字段的类型是numeric型而我在实体中用的是string型。
特别注意
当我们用泛型集合来代替DataTable的时候我们必须保证我们封装的实体的属性名和数据库中字段名一模一样,也就是说我们必须明确要转换的实体类的类型,否则会报错,另外我们在封装实体的属性的类型必须和我们数据库中相应字段的属性一样。
小结
当我们遇到新的知识的时候不要轻易的放过,我们需要自己查询一些资料然后通过实践慢慢的来理解这新知识的好处,好好的体会为什么其他人会这么用?其实就单纯工编码的难以来说DataTable比泛型集合容易的多,但是人们选择用一个比较难的方式来实现,其中一定有很多你想不到也就是值得你探索的知识,在重构的过程中功能实现并不是很重要,重要的是我们应该从第一遍的基础上加上以前学习的知识,用一种新的方式来实现功能,在这过程中不断探索,不断收获!