昨天找坤哥看到我的一段代码,如下:
稍微解释下,这段代码时D层查询结束后,将datatable查询到的结果赋值给实体对象的属性,然后返回实体的过程,坤哥看了之后问我,如果实体有500多个属性,难道也要这样一条一条的写吗?如果返回多个实体时怎么办?这时,我才意识到自己的代码时非常有问题的,原来设计的是每个方法最多返回一个实体,但是当遇到查询到多条记录的时候,就又冒着破坏三层结构的事返回Datatable去了,真的是很有问题啊。
怎么改,我脑海中一下子就浮现了老办法:数组+循环,用循环读条读取列值,保存到对象数组。之后查了查,发现我太OUT了。明明就有又新又好的技术可我用过了也想不起来。
百度上是用的是这样的:在实体层添加一个工具类,类似D层的工具类,只不过这个工具类是完成将datatable转换为一个包含实体的泛型集合的,然后每次返回的时候,将集合作为结果返回。
这个方法最好的地方还是在对每个实体属性的赋值上面,通过内部的属性集合循环赋值,再也不用一条条写了,这样,不仅封装性好了,而且使代码更不容易出错。
但是,唯一遗憾的就是,使用这个工具类对于实体和datatable有一个条件限制,就是实体的属性名必须和datatable的列名对应。
在百度上找了很多这样的类,但是只找到了C#版的,然后自己改写了个VB.NET版的。下面写出自己的DEMO:
首先,是实体类:
这个实体类是对教师信息表的映射。
Public Class EntityUser Private FilUserName As String '用户名 Private FilPwd As String '密码 Private FilTeaName As String '教师姓名 Private FilTeaLevel As String '教师级别 Private FilRegDate As String '注册日期 Private FilRegTime As String '注册时间 Private FilDelDate As String '注销日期 Private FilDelTime As String '注销时间 Private FilOnLineStatue As String '在线状态 ''' <summary> ''' 用户名 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property UserName() As String Get Return FilUserName End Get Set(ByVal value As String) FilUserName = value End Set End Property ''' <summary> ''' 密码 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property Pwd() As String Get Return FilPwd End Get Set(ByVal value As String) FilPwd = value End Set End Property ''' <summary> ''' 教师姓名 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property TeaName() As String Get Return FilTeaName End Get Set(ByVal value As String) FilTeaName = value End Set End Property ''' <summary> ''' 教师级别 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property TeaLevel() As String Get Return FilTeaLevel End Get Set(ByVal value As String) FilTeaLevel = value End Set End Property ''' <summary> ''' 注册日期 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property RegDate() As String Get Return FilRegDate End Get Set(ByVal value As String) FilRegDate = value End Set End Property ''' <summary> ''' 注册时间 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property RegTime() As String Get Return FilDelDate End Get Set(ByVal value As String) FilDelTime = value End Set End Property ''' <summary> ''' 注销日期 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property DelDate() As String Get Return FilDelDate End Get Set(ByVal value As String) FilDelDate = value End Set End Property ''' <summary> ''' 注销时间 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property DelTime() As String Get Return FilDelTime End Get Set(ByVal value As String) FilDelTime = value End Set End Property ''' <summary> ''' 在线状态 ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property OnLineStatue() As String Get Return FilOnLineStatue End Get Set(ByVal value As String) FilOnLineStatue = value End Set End Property End Class
我 的教师信息表如下:里面有3条记录,这个结果等一会咱们运行的时候会用到。
然后,写了个SqlHelper,这个代码不再写。因为这里是做个DEMO,所以没有采用3层架构,大家做的时候要注意不要破坏整体的结构。
然后下面就是本文主要要讨论的EntityHelper,不知道这样取名准不准确,但是如果要把这个类加入三层里面,我会把向SQLHelper那样对待,将它作为实体层的一个工具类加进去。
Imports System.Collections.Generic '增加泛型的命名空间 Imports System.Reflection '引入反射:为了使用PropertyInfo Public Class EntityHelper Public Shared Function convertToList(Of T As {New})(ByVal dt As DataTable) As IList(Of T) '将datatable转化为泛型集合 '注意:1,convertToList(Of T As {New}) 这里的new是用来约束T的,必须有,不然new T的时候会出现错误 '2,new约束在C#和VB.NET里面的写法是不一样的,C#里面用的是where来为T加上约束的 Dim myList As 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 myList.Add(myT) '添加到集合 Next Return myList '返回实体集合 End Function End Class
至此,主要的类都写完了,下面来看下客户端该如何调用:
<span style="font-size:18px;"> Dim strSQL As String = "select * from T_TeaInfo where TeaLevel ='管理员'" '注意,这种破坏架构的写法不提倡 Dim mysqlhelper As New SQLHelper '定义查询助手类 Dim dt As New DataTable '定义查询到的表集 Dim myList As List(Of EntityUser) '保存转换后的泛型集合 dt = mysqlhelper.ExecSelect(strSQL, CommandType.Text) '执行查询 '将dt转换为泛型集合 myList = EntityHelper.convertToList(Of EntityUser)(dt) TextBox1.Text = myList.Count '在文本框里面显示出查询到的教师数目 </span>
运行后,可以看到,结果如下:
小结下:
对于三层中,可能刚开始做的时候,我们从D层向上层传数据的时候用的是Datatable,只能说,这是一种非常破坏结构的方法,自己真的体会到了实体的好处,才会用它。