三层架构中使用实体类填充泛型集合代替DataTable解决方案(ASP.NET+C#)

20 篇文章 0 订阅
用三层架构开发项目,经常会遇到如下场景:

      D层负责与数据库交互,一般是得到DataTable或DataSet对象,然后返回给B层,B层进行类似的处理来读取数据:dt.Rows[0][“xxx”];或者dt.Rows[0][1];(强烈不建议使用)。

      有时DataTable也会被传到UI层,与控件进行绑定,显示数据。例如ASP.NET的repeater控件提取数据:<%# Eval(“xxx”)%>。

      无论是何种情况,使用DataTable不可避免的要填写读取的字段,这样做的坏处不言而喻:

      |  非常容易写错,而且编译器不检查。

      |  必须了解数据库的结构。

      |  不符合面向对象编程思想。

      |  DataTable为弱类型,无法直观的看出字段的数据类型。

      这个问题一直困扰着我,这次借着重构机房收费系统的机会,研究了一下这个问题,找到了一种比较好的解决方案:在D层把DataTable转换成单个实体类,再把实体类填充到泛型集合中。


核心思想如图:



     实体类即数据库的映射,因此实体类中的属性和数据库表中的字段是相对应的。把DataTable中的每一行记录视为一个实体类,把其中的字段读取出来,存放到实体类的属性中,再把所有的实体类存在泛型集合中。因此,DataTable中有多少个记录,泛型集合中就有多少个实体类,每个实体类的属性和DataTable的字段是相对应的。

     试想一下,这样一来,传到B层或U层的将是一个实体类集合,读取数据将会是如下场景:list[0].xxx;

     这样做的优点如下:

     |  编写B层的人员无需手动填写需要的字段,直接按一下点,全都提示出来了,想用哪个用哪个,不会出现写错的情况。

     |  不必了解数据库结构。

     |  符合面向对象思想。

     |  实体类的属性是强类型,每个字段的类型都是已知的。

 

     那么用代码如何实现呢?下面一一列举。

     注意:以下代码仅仅是为了模拟,有些不规范的地方,代码注释中有提示,切勿模仿!

 

实体类代码:


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Entity  
  7. {  
  8.     /// <summary>  
  9.     /// Users表实体类  
  10.     /// </summary>  
  11.     public class Users  
  12.     {  
  13.         private long _id;  
  14.         private string _userName;  
  15.         private string _passWord;  
  16.   
  17.         public long id   
  18.         {  
  19.             set {_id = value; }  
  20.             get { return _id; }  
  21.         }  
  22.   
  23.         public string userName   
  24.         {  
  25.             set { _userName = value; }  
  26.             get { return _userName; }  
  27.         }  
  28.   
  29.         public string passWord   
  30.         {  
  31.             set { _passWord = value; }  
  32.             get { return _passWord; }  
  33.         }  
  34.     }  
  35. }  


将DataTable转换成List<T>泛型集合助手类,这个类我放在了Entity实体类层中:


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Data;  
  6. using System.Data.SqlClient;  
  7. using System.Reflection;  
  8.   
  9. namespace Entity  
  10. {  
  11.     /// <summary>  
  12.     /// 将DataTable转换成泛型集合IList<>助手类  
  13.     /// </summary>  
  14.     public class ConvertHelper  
  15.     {  
  16.         /// <summary>  
  17.         /// 单表查询结果转换成泛型集合  
  18.         /// </summary>  
  19.         /// <typeparam name="T">泛型集合类型</typeparam>  
  20.         /// <param name="dt">查询结果DataTable</param>  
  21.         /// <returns>以实体类为元素的泛型集合</returns>  
  22.         public static IList<T> convertToList<T>(DataTable dt) where T : new()  
  23.         {  
  24.             // 定义集合  
  25.             List<T> ts = new List<T>();  
  26.   
  27.             // 获得此模型的类型  
  28.             Type type = typeof(T);  
  29.             //定义一个临时变量  
  30.             string tempName = string.Empty;  
  31.             //遍历DataTable中所有的数据行   
  32.             foreach (DataRow dr in dt.Rows)  
  33.             {  
  34.                 T t = new T();  
  35.                 // 获得此模型的公共属性  
  36.                 PropertyInfo[] propertys = t.GetType().GetProperties();  
  37.                 //遍历该对象的所有属性  
  38.                 foreach (PropertyInfo pi in propertys)  
  39.                 {  
  40.                     tempName = pi.Name;//将属性名称赋值给临时变量    
  41.                     //检查DataTable是否包含此列(列名==对象的属性名)      
  42.                     if (dt.Columns.Contains(tempName))  
  43.                     {  
  44.                         // 判断此属性是否有Setter    
  45.                         if (!pi.CanWrite) continue;//该属性不可写,直接跳出    
  46.                         //取值    
  47.                         object value = dr[tempName];  
  48.                         //如果非空,则赋给对象的属性    
  49.                         if (value != DBNull.Value)  
  50.                             pi.SetValue(t, value, null);  
  51.                     }  
  52.                 }  
  53.                 //对象添加到泛型集合中  
  54.                 ts.Add(t);  
  55.             }  
  56.   
  57.             return ts;    
  58.         }  
  59.     }  
  60. }  

ASP.NET Web页面后台代码(aspx.cs):


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Web.UI;  
  6. using System.Web.UI.WebControls;  
  7. using Entity;  
  8. using System.Data;  
  9. using System.Data.SqlClient;  
  10.   
  11. public partial class _Default : System.Web.UI.Page  
  12. {  
  13.     protected void Page_Load(object sender, EventArgs e)  
  14.     {  
  15.         if (!Page.IsPostBack)   
  16.         {  
  17.             DataTable dt = new DataTable();  
  18.             IList<Users> users = new List<Users>();  
  19.              
  20.             //注意:只是为了演示,才在这读数据库的!  
  21.             //在项目中千万不要这么做!另外sql链接字符串也不应该写在程序中,而且要加密。  
  22.             SqlConnection sqlCon = new SqlConnection("server=192.168.24.177;database=testDB;uid=sa;pwd=123");  
  23.             SqlCommand sqlCmd = new SqlCommand("select userName,passWord from t_testTable", sqlCon);  
  24.             SqlDataReader sqlRd;  
  25.             sqlCon.Open();  
  26.             sqlRd = sqlCmd.ExecuteReader();  
  27.             dt.Load(sqlRd); //DataTable构造完成,在这仅仅是为了演示,构造DataTable  
  28.             sqlCon.Close();  
  29.   
  30.             users = ConvertHelper.convertToList<Users>(dt); //获取DataTable转换成的泛型集合  
  31.             repUsers.DataSource = users;  
  32.             repUsers.DataBind();  
  33.         }  
  34.     }  
  35. }  

ASP.NET Web页面前台代码(aspx):
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml">  
  6. <head runat="server">  
  7.     <title></title>  
  8. </head>  
  9. <body>  
  10.     <form id="frmDefault" runat="server">  
  11.         <div>   
  12.             <asp:Repeater ID="repUsers" runat="server">  
  13.                 <ItemTemplate>  
  14.                     <table>  
  15.                         <tr>  
  16.                             <td><%# ((Entity.Users)Container.DataItem).id.ToString() %></td>  
  17.                             <td><%# ((Entity.Users)Container.DataItem).userName %></td>  
  18.                             <td><%# ((Entity.Users)Container.DataItem).passWord %></td>  
  19.                         </tr>  
  20.                     </table>  
  21.                 </ItemTemplate>  
  22.             </asp:Repeater>  
  23.         </div>  
  24.     </form>  
  25. </body>  
  26. </html>  

     这段代码在Web后台读取了数据中的数据,当然,只是为了模拟,这样写是非常不可取的。得到DataTable后利用ConvertHelper转换助手类的convertToList方法将DataTable转换成本例中的Users类型的泛型集合。最后把该集合与Repeater控件绑定,显示数据。

     Repeater之类的控件均支持绑定集合,而且支持的如此完美。个人认为界面显示那段代码是最漂亮的。

     例如:<%#((Entity.Users)Container.DataItem).id.ToString() %>。这和以往的显示方法大不相同。首先,没有使用Eval。数据是从Container.DataItem属性中读取的,强制转换为实体类类型,然后直接调用实体类的id属性。整个代码行云流水般使用,完全不需要参照数据库,想用哪个属性用哪个。另外,这样读取效率比用Eval高。

     在此,需要说明的是,我在这写的DataTable到List<T>的类是比较可靠的。类似ConvertHelper.convertToList<Users>(dt),想把DataTable转换成哪种实体类,调用方法的时候泛型参数(尖括号里的)就写哪个实体类,在这是Users。简单说一下它的转换原理。

     它会自动获取实体类的属性名,然后DataTable中匹配有没有该名称的字段,有的话就赋值。DataTable中的每一行记录都这样处理。

     基于以上原理,使用本方法必须满足以下条件,也可以说是注意事项吧:

      |  实体类的属性名必须和数据库表中的字段名一模一样。

     |  想用这种方法把DataTable转换成实体类,必须有已知的实体类和DataTable中的数据想对应,也就是说必须明确你要转换成的实体类类型,否则没办法指定泛型集 合的类型,也就没办法调用。

     |  各个表的字段尽量区别开来,不要相同。比如A表有个name字段,B表也有一个name字段,就不合理了。

        

     相信你看到这已经跃跃欲试了。可仔细思考一下,会发现一个大问题:这里涉及的仅仅是单表查询,联合查询显然搞不定。

     联合查询搞不定的主要原因是它的数据来自多个表,没有实体类与之对应,没有实体类就没有办法转换。

     目前比普遍的解决方案就是创建一个包含多个表的实体类,只要实体类中包含联合查询的这些表就可以,实体类中表示的表多了没事。但是,很多种联合查询,就要建立很多实体类,这也是没有办法的事。

     也有比较变态的方法就是创建一个通用实体类,包含所有表的字段。

     要想用此方法,创建新的实体类是必然的了。但即使是这样,我们也可以节省代码,避免重复。

     节省代码首先想到的是继承,可惜C#不支持多重继承,两个以上实体类组合成一个实体类就没辙了。用接口虽然可以实现多重继承,但显然不合理,接口不是干这个用的,这样一点代码也没省下,反而麻烦了。

     经过网友RenYue的提示,用聚合解决是种比较好的方案。

     聚合核心思想是:假如五个表联合查询,那么新建一个实体类,这个新实体类的属性是这个五个表对应的实体类,即用实体类封装实体类,我们暂且叫它聚合实体类

     这个想法很好,很好的复用了代码,但是实现起来比较复杂。刚刚提供的单个表查询的转换方法(convertToList),显然满足不了。

     经过研究,我确定没办法写一个通用的多表查询转换方法。但是可以实现确定个数的转换方法。我写了一个两个表联合查询的转换方法。

     把DataTable每一行记录中的字段分解给所属表对应的实体类,然后把这些实体类横向聚合到聚合实体类的属性中。如图:



为了演示,程序做如下变动:

 

在Entity项目中添加一个LoginLog类,用户登录日志:


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Entity  
  7. {  
  8.     /// <summary>  
  9.     /// LoginLog表实体类  
  10.     /// </summary>  
  11.     public class LoginLog  
  12.     {  
  13.         private long _id;  
  14.         private string _userName;  
  15.         private DateTime _loginTime;  
  16.         private DateTime _logoutTime;  
  17.   
  18.         public long id   
  19.         {  
  20.             get { return _id; }  
  21.             set { _id = value; }  
  22.         }  
  23.   
  24.         public string userName  
  25.         {  
  26.             get { return _userName; }  
  27.             set { _userName = value; }  
  28.         }  
  29.   
  30.         public DateTime loginTime   
  31.         {  
  32.             get { return _loginTime; }  
  33.             set { _loginTime = value; }  
  34.         }  
  35.   
  36.         public DateTime logoutTime  
  37.         {  
  38.             get { return _logoutTime; }  
  39.             set { _logoutTime = value; }  
  40.         }  
  41.     }  
  42. }  

在Entity项目中添加一个UsersJionLog类,该实体类用于Users表和LoginLog表联合查询,它的属性是Users类和LoginLog类:


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Entity  
  7. {  
  8.     /// <summary>  
  9.     /// UsersJionLog表实体类,该实体类用于Users表和LoginLog表联合查询  
  10.     /// </summary>  
  11.     public class UsersJionLog  
  12.     {  
  13.         private Users _users;  
  14.         private LoginLog _loginLog;  
  15.   
  16.         public Users users   
  17.         {  
  18.             set { _users = value; }  
  19.             get { return _users; }  
  20.         }  
  21.   
  22.         public LoginLog loginLog  
  23.         {  
  24.             set { _loginLog = value; }  
  25.             get { return _loginLog; }  
  26.         }  
  27.     }  
  28. }  

在ConvertHelper类中添加两个表联合查询的转换方法:


  1. /// <summary>  
  2. /// 两表查询结果转换成泛型集合  
  3. /// </summary>  
  4. /// <typeparam name="T">包含了两个表的实体类,聚合实体类</typeparam>  
  5. /// <typeparam name="U">实体类中的第一个实体类</typeparam>  
  6. /// <typeparam name="V">实体类中的第二个实体类</typeparam>  
  7. /// <param name="dt">从数据库中查询获得的DataTable</param>  
  8. /// <returns>以实体类为元素的泛型集合</returns>  
  9. public static IList<T> convertToJiontList<T, U, V>(DataTable dt)  
  10.     where T : new()  
  11.     where U : new()  
  12.     where V : new()  
  13. {  
  14.     //定义一个聚合实体类泛型集合,用来做返回值  
  15.     List<T> ts = new List<T>();  
  16.     //定义一个泛型集合元素,用来填充集合  
  17.     T t = new T();  
  18.     //获取聚合实体类的属性  
  19.     PropertyInfo[] propertys = t.GetType().GetProperties();  
  20.     //利用单表转换,把DataTable数据填充到聚合实体类中第一个实体类  
  21.     IList<U> uList = new List<U>();  
  22.     uList = convertToList<U>(dt);  
  23.     //利用单表转换,把DataTable数据填充到聚合实体类中第二个实体类  
  24.     IList<V> vList = new List<V>();  
  25.     vList = convertToList<V>(dt);  
  26.   
  27.     //经过以上两步,uList和vList两个集合的长度肯定是相同的  
  28.   
  29.     //把两个实体类填充到聚合实体类中  
  30.     for (int i = 0; i < uList.Count; i++)   
  31.     {  
  32.         propertys[0].SetValue(t, uList[i], null); //取uList中第i个元素,填充到聚合实体类的第一个属性中  
  33.         propertys[1].SetValue(t, vList[i], null); //取vList中第i个元素,填充到聚合实体类的第二个属性中  
  34.         ts.Add(t); //向聚合实体类集合中添加元素  
  35.     }  
  36.     return ts;  
  37. }  

ASP.NET Web后台(aspx.cs)代码:


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Web.UI;  
  6. using System.Web.UI.WebControls;  
  7. using Entity;  
  8. using System.Data;  
  9. using System.Data.SqlClient;  
  10.   
  11. public partial class _Default : System.Web.UI.Page  
  12. {  
  13.     protected void Page_Load(object sender, EventArgs e)  
  14.     {  
  15.         if (!Page.IsPostBack)   
  16.         {  
  17.             DataTable dt = new DataTable();  
  18.             IList<UsersJionLog> usersJionLog = new List<UsersJionLog>();  
  19.   
  20.             //注意:只是为了演示,才在这读数据库的!  
  21.             //在项目中千万不要这么做!另外sql链接字符串也不应该写在程序中,而且要加密。  
  22.             SqlConnection sqlCon = new SqlConnection("server=192.168.24.177;database=testDB;uid=sa;pwd=123");  
  23.             SqlCommand sqlCmd = new SqlCommand("select t_Users.userName,t_LoginLog.loginTime,t_LoginLog.logoutTime from t_Users join t_LoginLog on t_LoginLog.userName = t_Users.userName where t_Users.userName = 'admin'", sqlCon);  
  24.             SqlDataReader sqlRd;  
  25.             sqlCon.Open();  
  26.             sqlRd = sqlCmd.ExecuteReader();  
  27.             dt.Load(sqlRd); //DataTable构造完成,在这仅仅是为了演示,构造DataTable  
  28.             sqlCon.Close();  
  29.   
  30.             usersJionLog = ConvertHelper.convertToJiontList<UsersJionLog,Users,LoginLog>(dt);  
  31.   
  32.             repUsers.DataSource = usersJionLog;  
  33.             repUsers.DataBind();  
  34.         }  
  35.     }  
  36. }  

ASP.NET Web前台(aspx):


  1. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml">  
  6. <head runat="server">  
  7.     <title></title>  
  8. </head>  
  9. <body>  
  10.     <form id="frmDefault" runat="server">  
  11.         <div>   
  12.             <asp:Repeater ID="repUsers" runat="server">  
  13.                 <ItemTemplate>  
  14.                     <table>  
  15.                         <tr>  
  16.                             <td><%# ((Entity.UsersJionLog)Container.DataItem).users.userName %></td>  
  17.                             <td><%# ((Entity.UsersJionLog)Container.DataItem).loginLog.loginTime.ToString() %></td>  
  18.                             <td><%# ((Entity.UsersJionLog)Container.DataItem).loginLog.logoutTime.ToString() %></td>  
  19.                         </tr>  
  20.                     </table>  
  21.                 </ItemTemplate>  
  22.             </asp:Repeater>  
  23.         </div>  
  24.     </form>  
  25. </body>  
  26. </html>  

     这样就可以啦。

     需要解释的有两个地方:

     ConvertHelper.convertToJiontList<UsersJionLog,Users,LoginLog>(dt);这句调用代码,注意该方法需要三个类型,这三个类型第一个是返回的实体类类型,也就是聚合实体类;第二个参数是聚合实体类中的第一个属性的类型;第三个参数是聚合类中第二个属性的类型。必须一一对应,否则没法转换!

     另外Web界面上的调用代码,例如<%#((Entity.UsersJionLog)Container.DataItem).users.userName %>,这句话就是先把Container.DataItem转换成聚合实体类UsersJionLog,然后读取聚合实体类中的users属性,这个属性也是一个类(Users类),因此有自己的属性userName。

     这样就实现了多表联合查询的转换。聚合类代表了联合查询的所有表,想读取其中的哪个表,点一下选出来,然后再点一下就可以读出这个表中的字段了,相当好用!

     根据两个表联合查询的转换思路,我们可以写出N个表联合查询的转换方法,但是没办法通用,因为泛型的的类型必须是已知的,相信读者可以体会到。


     写的比较仓促,希望对大家有所帮助!

     有问题欢迎和我交流!

 

附:

 

本文所用数据库创建脚本:


  1. create database testDB;  
  2. use testDB;  
  3. create table t_Users(  
  4.     id bigint IDENTITY(1,1) PRIMARY KEY,  
  5.     userName varchar(100) NOT NULL UNIQUE,  
  6.     passWord varchar(100) NOT NULL  
  7. );  
  8. insert into t_Users(userName,passWordvalues('admin','123');  
  9. insert into t_Users(userName,passWordvalues('guest','456');  
  10.   
  11. create tabLe t_LoginLog(  
  12.     id bigint IDENTITY(1,1) PRIMARY KEY,  
  13.     userName varchar(100),  
  14.     loginTime datetime NOT NULL,  
  15.     logoutTime datetime NOT NULL,  
  16.     CONSTRAINT FK_LoginLog_userName FOREIGN KEY(userName) REFERENCES t_Users(userName)  
  17. );  
  18.   
  19. insert into t_LoginLog(userName,loginTime,logoutTime)values('admin','2012-7-30','2012-7-30');  
  20. insert into t_LoginLog(userName,loginTime,logoutTime)values('guest','2012-8-30','2012-8-30');  
  21. insert into t_LoginLog(userName,loginTime,logoutTime)values('admin','2012-9-30','2012-9-30');  
  22. insert into t_LoginLog(userName,loginTime,logoutTime)values('guest','2012-10-30','2012-10-30');  

本文示例完整源码:

点我下载

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值