(精彩转载)ADO.NET(三)
DataSet与SqlDataAdapter对象是微软在ADO.NET中推出的新一代的数据访问方式,有些情况下非常适合使用 DataSet,例如在设计原型、开发小型系统和支持实用程序时。但是,在企业系统中使用 DataSet 可能并不是最佳的解决方案,因为对企业系统来说,易于维护要比投入市场的时间更重要。因此在这里我们探讨 DataSet 的一种替代解决方案,即:自定义实体与集合
一、DataSet 存在的问题
问题一:硬编码问题
DataSet 和数据库不仅共享数据,不幸的是,它们还共享架构。
如:(
数据访问的C#代码:
public DataSet GetAllUsers()
{
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command = new SqlCommand("Select * from Users", connection);
command.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(command);
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}
页面代码:
<form id="Form1" method="post" runat="server">
<asp:Repeater ID="users" Runat="server">
<ItemTemplate>
<%# DataBinder.Ev
<br />
</ItemTemplate>
</asp:Repeater>
</form>
另外,这种编码方式还必须使用户层知道数据库的结构,当然也有人提出了使用强类型DataSet和常量解决这个问题。但这里的编码语法会使程序员很烦。如:ds.Tables[0].Rows[i]["userId"].ToString()不仅难于阅读,而且需要非常熟悉列名称及其类型。如果像这样使用 DataSet,业务层可能会很薄弱或很复杂。
问题二:弱类型的问题
DataSet的数据表中的值都以 System.Object 的形式返回,需要对这种值进行转换,但转换可能会失败的风险(如:空值转换,类型不匹配转换,标识符不正确等),不幸的是,失败不是在编译时发生,而是在运行时发生。另外,在转换的时候,我们可能会遇到拆箱和装箱的操作这种操作会比较耗费资源,降低软件性能。
问题三:面向对象的桎梏
DataSet虽然是个对象,但是这个对象还是从数据库中的二维关系表演化过来的,我们的一个实体对象在DataSet中却变成数据表中的一条记录,而对象与对象的关系却变成数据表与数据表之间的数据关系(DataRelation)。而我们面向对象的思想是在程序代码中模拟现实中的对象来构建程序中的对象的属性和方法,然后建立程序对象之间的引用与派生关系。
正因如此,我们的程序代码很难如实反应我们现实生活中的对象及对象与对象之间的联系。
所以说DataSet这个东西能很好地理解数据库,但不能很好地理解我们的生活。
二、自定义实体类:(
自定义实体类是我们根据现实生活中的对象抽象出来的类,它的对象能很好地反应我们的生活。它很好地利用继承和封装等 OO 技术。
如:
public class User
{
private string userName;
private string password;
public string UserName
{
get { return userName; }
set { userName = value; }
}
public string Password
{
get { return password; }
set { password = value; }
}
public User() {}
public User(string name, string password)
{
this.UserName = name;
this.Password = password;
}
}
简单的实体类的对象可对应表中的一条记录,其属性可对应于该记录的字段值。而实体类中对其它实体类的引用关联可对应于表与表之间的关联关系。
下面我们看一下如何把数据库中的结构与数据转换到我们的实体对象中去。
public User GetUser(string username)
{
SqlConnection conn = new SqlConnection("server=.;database=mydb;uid=sa;pwd=sa");
SqlCommand command = new SqlCommand("select * from username where username = @username", connection);
command.Parameters.AddWithValue("@username", username);
SqlDataReader dr = null;
conn.Open();
dr = command.ExecuteReader();
if (dr.Read())
{
User user = new User();
user.UserName = Convert.ToString(dr["UserName"]);
user.Password = Convert.ToString(dr["Password"]);
conn.Close();
return user;
}
conn.Close();
return null;
}
细心的朋友可能注意到我前面提到 DataSet 的问题:
弱类型,效率降低,并且存在数据库架构变化而转换出错的问题,还需要开发人员深入了解基础数据结构。
看一看上文的代码,您可能会注意到这些问题依然存在。但请注意,我们已经将这些问题封装到一个非常孤立的代码区域内;而并不像DataSet那样分布在整个程序代码当中。
好了,到此为至,我们对够把数据库中的一条记录转换为我们实体类对象了,可以当我们从数据库中检索出一批记录来,我们如何保存到内存中呢?答案当然是实体类的集合。
说到集合,我想大部分人会想到ArrayList,但现在问题来了,ArrayList本身就是一个弱类型的集合,即任何东西都可以放在ArrayList集合中去,而取数据的时候又要进行类型转换或拆箱装箱。这岂不是又重DataSet的复辙了吗?在DotNet1.1中,我们只好采用自定义集合类型(派生自CollectionBase类),来实现集合的强类型化。但由于实体类的多样性,也必然带来了实体类集合的多样性,使我们花费大量时间编写各种实体类的强类型集合。
好在DotNet2.0中为我们提供了一个新的概念--泛型。它使人们从编写大量强类型的实体类集合的工作中解脱出来。在这里我们不多谈泛型这个概念,只来关注一下我们最常用的一个泛型集合类型--List<T>
三、泛型集合List<T>
List<T>这个泛型集合所在的命名空间是:System.Collections.Generic,它是一个强类型的集合,它还提供了一系列的集合操作方法,如添加、删除、检索、排序等。
其中的"T"中个类型“代位符”,当我们实例化List<T>的时候,会根据"T"的值,控制我们List<T>只存储相应类型的数据。
如:
List<User> list = new List<User>();
这句话就生成一个集合list,该集合中的每一个元素必须是User数据类型,如果试图把其它数据类型添加到我们的list集合中去会发生编译异常。另外在取得list集合中的某个元素的时候,也不必去进行强制转换,因为它存储进去的就是User类型。
如:
public List<User> GetUser(string username)
{
List<User> list = new List<User>();
SqlConnection conn = new SqlConnection("server=.;database=mydb;uid=sa;pwd=sa");
SqlCommand command = new SqlCommand("select * from username where username = @username", connection);
command.Parameters.AddWithValue("@username", username);
SqlDataReader dr = null;
conn.Open();
dr = command.ExecuteReader();
while(dr.Read())
{
User user = new User();
user.UserName = Convert.ToString(dr["UserName"]);
user.Password = Convert.ToString(dr["Password"]);
list.Add(user);
}
conn.Close();
return list;
}
在VS2005中我们的GridView和DataList都可以绑定到我们的实体类泛型集合上,就像使用DataSet绑定一样方便。)
在泛型集合中有的方法Sort()可以对里面的实体类进行排序,但是如果仅简单使用Sort()进行排序的话会产生异常。因为List<User>中所保存的是一个实体对象,究竟按照哪个实体属性进行排序List<User>对象现在并不知道。除非我们实体类实现IComparable<T>接口。
IComparable<T>接口中有个方法"int CompareTo(T)"需要我们实现,在该方法中编写代码,把当前对象与T对象相关属性值的进行对比,并根据对比的结果返回-1,1,0三个值,以做为List<T>对象对其内部实体类对象进行排序的依据。
如:
public class User : IComparable<User>
{
private string _UserName;
private string _Password;
private
public string UserName
{
get { return _UserName; }
set { _UserName = value; }
}
public string Password
{
get { return _Password; }
set { _Password = value; }
}
public User() {}
public User(string name, string password)
{
this.UserName = name;
this.Password = password;
}
public int CompareTo(User obj)
{
if(this.UserName > obj.UserName)
{
return 1;
}
else if(this.UserName < obj.UserName)
{
return -1;
}
else
{
return 0;
}
}
}
四、实体类之间的关系
对于关系数据库,可以通过外键维护关系。实体之间也存在关系,而在实体类中,关系则体现为一个实体类对另一个对象的引用。(
如:
public class Role : IComparable<Role>
{
private string _RoleId;
private string _RoleName;
public string RoleId
{
get{return _RoleId;}
set{_RoleId = value;}
}
public string RoleName
{
get{return _RoleName;}
set{_RoleName = value;}
}
public Role(){}
public Role(string roleid,string rolename)
{
this._RoleId = roleid;
this._RoleName = rolename;
}
public int CompareTo(Role obj)
{
if(this.RoleId > obj.RoleId)
return 1;
else if(this.RoleId < obj.RoleId)
return -1;
else
return 0;
}
}
public class User : IComparable<User>
{
private string _UserName;
private string _Password;
private List<Role> _Roles;
public string UserName
{
get { return _UserName; }
set { _UserName = value; }
}
public string Password
{
get { return _Password; }
set { _Password = value; }
}
public List<Role> Roles
{
get{ return _Role;}
set{_Role = value;}
}
public User() {}
public User(string name, string password)
{
this.UserName = name;
this.Password = password;
}
public int CompareTo(User obj)
{
if(this.UserName > obj.UserName)
{
return 1;
}
else if(this.UserName < obj.UserName)
{
return -1;
}
else
{
return 0;
}
}
}
数据访问类:
//角色数据访问类
public class RoleDA
{
private SqlConnection conn = new Conn().Connection;
public RoleDA()
{
}
//根据角色代号返回角色实体对象
public RoleData select(string roleId)
{
RoleData rd = null;
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select * from [role] where roleid = @roleid";
cmd.Parameters.AddWithValue("@roleid",roleId);
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
rd = new RoleData();
rd.RoleId = dr["roleid"].ToString();
rd.RoleName = dr["rolename"].ToString();
}
conn.Close();
return rd;
}
}
//用户角色关系数据访问类
public class UserInRoleDA
{
private SqlConnection conn = new Conn().Connection;
public UserInRoleDA()
{
}
//根据用户名查询该用户的角色列表
public List<RoleData> select(string username)
{
List<RoleData> list = new List<RoleData>();
RoleData rd = null;
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select * from userinrole where username = @username";
cmd.Parameters.AddWithValue("@username",username);
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
//调用角色数据访问类生成角色实体对象
rd = new RoleDA().select(dr["roleid"].ToString());
list.Add(rd);
}
conn.Close();
return list;
}
}
//用户数据访问类
public class UserDA
{
private SqlConnection conn = new Conn().Connection;
public UserDA()
{
}
//根据用户名返回用户实体对)
public UserData select(string username)
{
UserData user = null;
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select * from [user] where username = @username";
cmd.Parameters.AddWithValue("@UserName",username);
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{
user = new UserData();
user.UserName = dr["username"].ToString();
user.Password = dr["password"].ToString();
//根据用户名调用用户角色关系数据访问类的方法生成用户拥有的角色列表
user.Roles = new UserInRoleDA().select(user.UserName);
}
conn.Close();
return user;
}
}
客户端代码:
UserData user = new UserDA().select("Fred L. Mannering");
Response.Write("UserName:"+user.UserName+"<br>");
Response.Write("Password:"+user.Password+"<br>");
foreach (RoleData role in user.Roles)
{
Response.Write(">>Role:"+role.RoleId.ToString()+"-"+role.RoleName+"<br>");
}
运行结果:
UserName:Fred L. Mannering
Password:aaa
>>Role:R002-Manager
>>Role:R003-Employee
自定义实体类使您获得了面向对象的编程的丰富功能,并帮助您构建了可靠、可维护的 N 层体系结构的框架,它也是目前业界所普遍采用的实现方式。