图5 远程处理数据读取器
该图着重显示了数据读取器如何存在于创建它的应用程序域中,以及对它的所有访问如何在客户端和服务器应用程序域之间产生往返行程。这意味着,只有当数据读取器在调用方所在的同一应用程序域中执行时,数据访问方法才应当返回这些数据读取器。在使用数据读取器时,还有其他两个需要考虑的问题。
首先,在从数据访问类中的方法返回数据读取器时,需要考虑与该数据读取器相关联的连接对象的生存期。默认情况下,在调用方循环访问数据读取器时,连接会保持繁忙。遗憾的是,当调用方完成工作时,连接将保持开启状态,因此不会返回到连接池(如果启用连接池的话)。但是,您可以通过将CommandBehavior.CloseConnection枚举值传递给命令对象的ExecuteReader方法,来指示数据读取器在它的Close方法被调用时关闭连接。
其次,为了将表示层从特定的Framework data provider(例如SqlClient或OleDb)分离,调用代码应当使用IDataReader接口而不是具体类型(例如SqlDataReader)来引用返回值。这样,如果应用程序从Oracle移植到SQL Server后端,并且数据访问类中的方法的返回类型进行了更改,则表示层无须更改。如果您希望数据访问类返回XML,则可以从System.Xml命名空间中的XmlDocument和XmlReader类(它们类似于DataSet和IDataReader)中进行选择。换句话说,当数据要从它的源断开连接时,您的方法应当返回XmlDocument(或XmlDataDocument),而XmlReader可以用来对XML数据进行流式访问。
最后,您还可以决定用公共属性返回自定义类。这些类可以用Serialization属性标记,以便能够跨应用程序域进行复制。另外,如果您要从方法中返回多个对象,则可能需要强类型集合类。
Imports System.Xml.Serialization<Serializable()> _Public Class Book : Implements IComparable<XmlAttributeAttribute()> Public ProductID As IntegerPublic ISBN As StringPublic Title As StringPublic Author As StringPublic UnitCost As DecimalPublic Description As StringPublic PubDate As DatePublic Function CompareTo(ByVal o As Object) As Integer _Implements IComparable.CompareTo Dim b As Book = CType(o, Book) Return Me.Title.CompareTo(b.Title)End FunctionEnd ClassPublic NotInheritable Class BookCollection : Inherits ArrayListDefault Public Shadows Property Item(ByVal productId As Integer)As BookGet Return Me(IndexOf(productId))End GetSet(ByVal Value As Book) Me(IndexOf(productId)) = ValueEnd SetEnd PropertyPublic Overloads Function Contains(ByVal productId As Integer) As Boolean Return (-1 <> IndexOf(productId))End FunctionPublic Overloads Function IndexOf(ByVal productId As Integer) As Integer Dim index As Integer = 0 Dim item As Book For Each item In Me If item.ProductID = productId Then Return index End If index = index + 1 Next Return -1End FunctionPublic Overloads Sub RemoveAt(ByVal productId As Integer) RemoveAt(IndexOf(productId))End SubPublic Shadows Function Add(ByVal value As Book) As Integer Return MyBase.Add(value)End FunctionEnd Class 图6 使用一个自定义类
图6包含了一个简单的Book类及其关联的集合类的示例。您会发现,Book类用Serializable进行了标记,以便跨应用程序域启用“按值(by value)”语义。该类实现了IComparable接口,以便当它包含在集合类中的时候,它将在默认情况下按Title排序。BookCollection类派生自System.Collections命名空间中的ArrayList,并且遮蔽了Item属性和Add方法,以便将集合限制为仅包含Book对象。通过使用自定义类,您可以获得对数据表示方法的完全控制,通过强类型化和IntelliSense提高开发人员的工作效率,并且消除对ADO.NET的调用方依赖性。但是,由于.NET Framework不包含任何与对象相关的映射技术(除了本质上是派生的DataSet类的类型化DataSet对象以外),因此该方法需要更多的代码。在这些情况下,您通常要在数据访问类中创建一个数据读取器,并且使用它来填充自定义类