打印本文 打印本文 关闭窗口 关闭窗口
掌握 ASP.NET 之路:自定义实体类简介
作者:武汉SEO闵涛  文章来源:敏韬网  点击数2901  更新时间:2009/4/23 10:36:06  文章录入:mintao  责任编辑:mintao
nteger) _userId = Value End Set End Property Public Property UserName() As String Get Return _userName End Get Set(ByVal Value As String) _userName = Value End Set End Property Public Property Password() As String Get Return _password End Get Set(ByVal Value As String) _password = Value End Set End Property #End Region #Region "Constructors" Public Sub New() End Sub Public Sub New(id As Integer, name As String, password As String) Me.UserId = id Me.UserName = name Me.Password = password End Sub #End Region End Class //C# public class User { #region "Fields and Properties" private int userId; private string userName; private string password; public int UserId { get { return userId; } set { userId = value; } } public string UserName { get { return userName; } set { userName = value; } } public string Password { get { return password; } set { password = value; } } #endregion #region "Constructors" public User() {} public User(int id, string name, string password) { this.UserId = id; this.UserName = name; this.Password = password; } #endregion }

为什么能够从它们获益?

使用自定义实体获得的主要好处来自这样一个简单的事实,即它们是完全受您控制的对象。具体而言,它们允许您:

?

利用继承和封装等 OO 技术。

?

添加自定义行为。

例如,我们的 User 类可以通过为其添加 UpdatePassword 函数而受益(我们可能会使用外部/实用程序函数对数据集执行此类操作,但会影响可读性/维护性)。另外,它们属于强类型,这表示我们可以获得 IntelliSense 支持:


1User 类的 IntelliSense

最后,因为自定义实体为强类型,所以不太需要进行容易出错的强制转换:

Dim userId As Integer = user.UserId
''''与
Dim userId As Integer = 
?         Convert.ToInt32(ds.Tables("users").Rows(0)("UserId"))
返回页首返回页首

对象关系映射

正如前文所讨论的那样,此方法的主要挑战之一就是处理关系数据和对象之间的差异。因为我们的数据始终存储在关系数据库中,所以我们只能在这两个世界之间架起一座桥梁。对于上文的 User 示例,我们可能希望在数据库中建立一个如下所示的用户表:


2User 的数据视图

从这个关系架构映射到自定义实体是一个非常简单的事情:

''''Visual Basic .NET
Public Function GetUser(ByVal userId As Integer) As User
Dim connection As New SqlConnection(CONNECTION_STRING)
Dim command As New SqlCommand("GetUserById", connection)
command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId
Dim dr As SqlDataReader = Nothing
Try
connection.Open()
dr = command.ExecuteReader(CommandBehavior.SingleRow)
If dr.Read Then
Dim user As New User
user.UserId = Convert.ToInt32(dr("UserId"))
user.UserName = Convert.ToString(dr("UserName"))
user.Password = Convert.ToString(dr("Password"))
Return user
End If
Return Nothing
Finally
If Not dr is Nothing AndAlso Not dr.IsClosed Then
dr.Close()
End If
connection.Dispose()
command.Dispose()
End Try
End Function

//C#
public User GetUser(int userId) {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command = new SqlCommand("GetUserById", connection);
command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId;
SqlDataReader dr = null;
try{
connection.Open();
dr = command.ExecuteReader(CommandBehavior.SingleRow);
if (dr.Read()){
User user = new User();
user.UserId = Convert.ToInt32(dr["UserId"]);
user.UserName = Convert.ToString(dr["UserName"]);
user.Password = Convert.ToString(dr["Password"]);
return user;            
  }
return null;
}finally{
if (dr != null && !dr.IsClosed){
dr.Close();
  }
connection.Dispose();
command.Dispose();
 }
}

我们仍然按照通常的方式设置连接和命令对象,但接着创建了 User 类的一个新实例并从 DataReader 中填充该实例。您仍然可以在此函数中使用 DataSet 并将其映射到您的自定义实体,但 DataSet 相对于 DataReader 的主要好处是前者提供了数据的断开连接的视图。在本例中,User 实例提供了断开连接的视图,使我们可以利用 DataReader 的速度。

等一下!您并没有解决任何问题!

细心的读者可能注意到我前面提到 DataSet 的问题之一是它们并非强类型,这导致效率降低并增加了出现运行时错误的可能性。它们还需要开发人员深入了解基础数据结构。看一看上文的代码,您可能会注意到这些问题依然存在。但请注意,我们已经将这些问题封装到一个非常孤立的代码区域内;这表示您的类实体的使用者(Web 界面、Web 服务使用者、Windows 表单)仍然完全没有意识到这些问题。相反,使用 DataSet 可以将这些问题分散到整个代码中。

改进

上文的代码对显示映射的基本概念很有用,但可以在两个关键的方面进行改进。首先,我们需要提取并将代码填充到其自己的函数中,因为代码有可能会被重新使用:

''''Visual Basic .NET
Public Function PopulateUser(ByVal dr As IDataRecord) As User
Dim user As New User
user.UserId = Convert.ToInt32(dr("UserId"))
''''检查 NULL 的示例
If Not dr("UserName") Is DBNull.Value Then
user.UserName = Convert.ToString(dr("UserName"))
End If
user.Password = Convert.ToString(dr("Password"))
Return user
End Function

//C#
public User PopulateUser(IDataRecord dr) {
User user = new User();
user.UserId = Convert.ToInt32(dr["UserId"]);
//检查 NULL 的示例
if (dr["UserName"] != DBNull.Value){
user.UserName = Convert.ToString(dr["UserName"]);   
 }
user.Password = Convert.ToString(dr["Password"]);
return user;
}

第二个需要注意的事项是,我们不对映射函数使用 SqlDataReader,而是使用 IDataRecord。这是所有 DataReader 实现的接口。使用 IDataRecord 使我们的映射过程独立于供应商。也就是说,我们可以使用上一个函数从 Access 数据库中映射 User,即使它使用 OleDbDataReader 也可以。如果您将这个特定的方法与 Provider Model Design Pattern(链接 1链接 2)结合使用,您的代码就可以轻松地用于不同的数据库提供程序。

最后,以上代码说明了封装的强大功能。处理 DataSet 中的 NULL 并非最简单的事,因为每次提取值时都需要检查它是否为 NULL。使用上述填充方法,我们在一个地方就轻松地解决了此问题,使我们的客户无需处理它。

映射到何处?

关于此类数据访问和映射函数的归属问题存在一些争论,即究竟是作为独立类的一部分,还是作为适当自定义实体的一部分。将所有用户相关的任务(获取数据、更新和映射)都作为 User 自定义实体的一部分当然很不错。这在数据库架构与自定义实体很相似时会很有用(比如在本例中)。随着系统复杂性的增加,这两个世界的差异开始显现出来,将数据层和业务层明确分离对简化维护有很大的帮助(我喜欢将其称为数据访问层)。将访问和映射代码放在其自己的层 (DAL) 上有一个副作用,即它为确保数据层与业务层的明确分离提供了一个严格的原则:

永远不要从 System.Data 返回类或从 DAL 返回子命名空间

返回页首返回页首

自定义集合

到目前为止,我们只了解了如何处理单个实体,但您经常需要处理多个对象。一个简单的解决方案是将多个值存储在一个一般的集合(例如 Arraylist)中。这并非最理想的解决方案,因为它又产生了与 DataSet 有关的一些问题,即:

?

它们不是强类型,并且

?

无法添加自定义行为。

最能满足我们需求的解决方案是创建我们自己的自定义集合。幸亏 Microsoft .NET Framework 提供了一个专门为了此目的而继承的类:CollectionBaseCollectionBase 的工作原理是,将所有类型的对象都存储在专有 Arraylist 中,但是通过只接受特定类型(例如 User 对象)的方法来提供对这些专有集合的访问。也就是说,将弱类型代码封装在强类型的 API 中。

虽然自定义集合可能看起来有很多代码,但大多数都可以由代码生成功能或通过剪切和粘贴方便地完成,并且通常只需要一次搜索和替换即可。让我们看一看构成 User 类的自定义集合的不同部分:

''''Visual Basic .NET
Public Class UserCollection
Inherits CollectionBase
Default Public Property Item(ByVal index As Integer) As User
Get
Return CType(List(index), User)
End Get
Set
List(index) = value
End Set
End Property
Public Function Add(ByVal value As User) As Integer
Return (List.Add(value))
End Function
Public Function IndexOf(ByVal value As User) As Integer
Return (List.IndexOf(value))
End Function
Public Sub Insert(ByVal index As Integer, ByVal value As User)
List.Insert(index, value)
End Sub
Public Sub Remove(ByVal value As User)
List.Remove(value)
End Sub
Public Function Contains(ByVal value As User) As Boolean
Return (List.Contains(value))
End Function
End Class

//C#
public class UserCollection :CollectionBase {
public User this[int index] {
get {return (User)List[index];}
set {List[index] = value;}
 }
public int Add(User value) {
return (List.Add(value));
 }
public int IndexOf(User value) {
return (List.IndexOf(value));
 }
public void Insert(int index, User value) {
List.Insert(index, value);
 }
public void Remove(User value) {
List.Remove(value);
 }
public bool Contains(User value) {
return (List.Contains(value));
 }
}

通过实现 CollectionBase 可以完成更多任务,但上面的代码代表了自定义集合所需的核心功能。观察一下 Add 函数,可以看出我们只是简单地将对 List.Add(它是一个 Arraylist)的调用封装到仅允许 User 对象的函数中。

映射自定义集合

将我们的关系数据映射到自定义集合的过程与我们对自定义实体执行的过程非常相似。我们不再创建一个实体并将其返回,而是将该实体添加到集合中并循环到下一个:

''''Visual Basic .NET
Public Function GetAllUsers() As UserCollection
Dim connection As New SqlConnection(CONNECTION_STRING)
Dim command As New SqlCommand("GetAllUsers", connection)
Dim dr As SqlDataReader = Nothing
Try
connection.Open()
dr = command.ExecuteReader(CommandBehavior.SingleResult)
Dim users As New UserCollection
While dr.Read()
users.Add(PopulateUser(dr))
End While
Return users
Finally
If Not dr Is Nothing AndAlso Not dr.IsClosed Then
dr.Close()
End If
connection.Dispose()
command.Dispose()
End Try
End Function

//C#
public UserCollection GetAllUsers() {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command =new SqlCommand("GetAllUsers", connection);
SqlDataReader dr = null;
try{
connection.Open();
dr = command.ExecuteReader(CommandBehavior.SingleResult);
UserCollection users = new UserCollection();
while (dr.Read()){
users.Add(PopulateUser(dr));
  }
return users;
}finally{
if (dr != null && !dr.IsClosed){
dr.Close();
  }
connection.Dispose();
command.Dispose();
 }
}

我们从数据库中获得数据、创建自定义集合,然后通过在结果中循环来创建每个 User 对象并将其添加到集合中。同样要注意 PopulateUser 映射函数是如何重新使用的。

添加自定义行为

在讨论自定义实体时,我们只是泛泛地提到可以将自定义行为添加到类中。您向实体中添加的功能类型很大程度上取决于您要实现的业务逻辑的类型,但您可能希望在自定义集合中实现某些常见的功能。一个示

上一页  [1] [2] [3] [4]  下一页

打印本文 打印本文 关闭窗口 关闭窗口