Enterprise Library 5.0 数据访问应用程序块:玩转 IDataReader
探索 Enterprise Library 5.0 数据访问应用程序块中的 IDataReader 的行为。
引言
如果您之前使用过 Microsoft Enterprise Library 数据访问应用程序块(5.0 或更早版本),您应该熟悉 ExecuteReader()
方法。Enterprise Library 5.0 文档将其中一个重载描述为:
ExecuteReader(DbCommand)
:执行命令并返回一个IDataReader
,可以通过它读取结果。调用者有责任在完成后关闭读取器。
这表明,首先,该方法返回的是 IDataReader
接口,而不是特定的 DataReader 类型,例如 ADO.NET 和 SQL Server 数据库中常用的 SqlDataReader
。其次,如果在应用程序中使用,必须关闭读取器,可以通过显式调用 reader.Close()
或隐式地将读取器的代码放在 using{}
块中,以释放读取器持有的数据库连接。
在 ASP.NET 应用程序中使用 IDataReader
似乎很简单。然而,在本文中您将看到,一些行为可能并非您所期望,且未被充分记录。已准备了一个演示应用程序来说明这些行为(使用 Visual Studio 2010 和 SQL Express 2008R2)。在演示中,使用数据访问应用程序块的数据访问被封装在一个静态类 DataObject
中,位于 DataAccessEntLib
命名空间下,该类提供了从 SQL Server 数据库检索数据以及执行数据/对象转换的多种方法。default.aspx 页面展示了使用 IDataReader
的五个示例。有些可以工作,有些则不行。让我们逐一来看。
从 IDataReader 读取数据
有了 Enterprise Library 5.0 数据访问应用程序块,应用程序与 SQL Server 数据库之间的交互变得相对简单。需要编写的代码量大大减少,代码也易于阅读和维护。在 DataObject
类中,使用以下代码返回一个 IDataReader
:
//return an IDataReader
//Param: connectionString - the actual connectionstring to database
//Param: sqlQuery - Sql select statement for retrieving data from database
public static IDataReader GetIDataReader(string connectionString, string sqlQuery)
{
SqlDatabase db = new SqlDatabase(connectionString);
DbCommand cmd = db.GetSqlStringCommand(sqlQuery);
//return an IDataReader.
return db.ExecuteReader(cmd);
}
只有三行代码:声明一个 SqlDatabase
,声明一个 DbCommand
,然后调用 ExecuteReader()
方法返回一个 IDataReader
。
在 default.aspx 页面的第一个示例中,应用程序直接从 IDataReader
读取数据并在页面上显示结果。以下是实现此功能的代码列表:
using (IDataReader IReader =
DataObject.GetIDataReader(ConnectionString, "select * from Customers"))
{
bool HeaderExists = false;
int NumberOfCols = 5;
while(IReader.Read())
{
if (!HeaderExists)//add table header
{
TableRow r0=new TableRow();
for (int i = 0; i < NumberOfCols; i++)
{
TableCell c = new TableCell();
c.Controls.Add(new LiteralControl(IReader.GetName(i).ToString()));
r0.Cells.Add(c);
}
tblResults.Rows.Add(r0);
HeaderExists = true;
}
TableRow r = new TableRow(); //add data
for (int i = 0; i < NumberOfCols; i++)
{
TableCell c = new TableCell();
c.Controls.Add(new LiteralControl(IReader[i].ToString()));
r.Cells.Add(c);
}
tblResults.Rows.Add(r);
}
}
应用程序调用 DataObject.GetIDataReader()
方法从数据库检索 IDataReader
,然后通过 while
语句循环遍历读取器,创建行和单元格,通过 ASP.NET Table
服务器控件显示数据。输出如图下截图所示。
它奏效了。正如您所见,应用程序能够像读取 SqlDataReader
中的数据一样,从 IDataReader
中读取数据。代码包含在一个 using{}
块中,以确保在到达数据末尾时 IDataReader
会被自动关闭。
将 IDataReader 绑定到 GridView
到目前为止一切顺利。我们能够从 IDataReader
读取数据。但是我们能将 IDataReader
绑定到 GridView
服务器控件吗?以下是 Enterprise Library 5.0 文档中的描述和代码示例:
从文档来看,我们似乎可以做到。让我们尝试一下,看看结果如何。在我们的 default.aspx 页面中,下面的代码用于将 IDataReader
绑定到 GridView
:
using (IDataReader IReader = DataObject.GetIDataReader(ConnectionString,
"select * from Customers"))
{
GridView1.DataSource = IReader;
GridView1.DataBind();
}
再次调用 DataObject.GetIDataReader()
方法获取 IDataReader
,然后将其分配给页面上的 GridView1
作为其数据源。执行后,我们没有看到结果,而是看到了一个错误消息:
它不起作用。错误表明 IDataReader
不是可用于 GridView1
数据源的有效类型。显然,在将其传递给 GridView
之前,需要将其转换为有效类型。根据我们的经验,ADO.NET SqlDataReader
肯定是一种有效类型,因为它经常用于数据绑定。让我们将 IDataReader
转换为 SqlDataReader
。
直接将 IDataReader 转换为 SqlDataReader
.NET Framework 中,使用“()
”运算符,通过类似以下的语法将一种数据类型/对象直接转换为另一种是很常见的:
double a = 1234.5;
int b = (int) a;
上面的代码将一个 double
转换为一个 int
。我们将执行类似的操作来将 IDataReader
转换为 SqlDataReader
。
在 DataObject
类中,提供了一个名为 DataObject.GetSqlDataReader()
的方法,其中第一行代码通过调用 GetIDataReader()
方法创建一个 IDataReader
,第二行代码通过直接将 IDataReader
转换为 SqlDataReader
来返回一个 SqlDataReader
。请参阅下面的代码列表:
//return a SqlDataReader
//Param: connectionString - the actual connectionstring to database
//Param: sqlQuery - Sql select statement for retrieving data from database
public static SqlDataReader GetSqlDataReader(string connectionString, string sqlQuery)
{
IDataReader IReader = GetIDataReader(connectionString, sqlQuery);
//return a SqlDataReader by directly casting the IDataReader to a SqlDateReader
return (SqlDataReader) IReader;
}
为了测试,我们只需将返回的 SqlDataReader
分配给 default.aspx 页面上的 GridView2
:
using (SqlDataReader MySqlDataReader=DataObject.GetSqlDataReader(
ConnectionString, "select * from Customers"))
{
GridView2.DataSource = MySqlDataReader;
GridView2.DataBind();
}
我们又收到了一个错误消息,与之前的不同:
它不起作用。实际上,这个错误消息相当令人困惑,因为出现了一种新的数据读取器类型 RefCountingDataReader
,而我们以为我们正在尝试将 IDataReader
转换为 SqlDataReader
。您会想这个读取器是从哪里来的。根据 MSDN 文档,事实证明 RefCountingDataReader
是 IDataReader
接口的默认实现。因此,我们需要深入研究 RefCountingDataReader
来找到一种转换方法。
顺便说一句,在 Enterprise Library 3.1 版本中,从 IDataReader
直接转换为 SqlDataReader
是可以的。如果您使用的是 Enterprise Library 的早期版本并计划升级,在将应用程序部署到服务器之前,彻底在 Enterprise Library 5.0 环境下测试您的应用程序可能是一个好主意。
正确地将 IDataReader 转换为 SqlDataReader
在线搜索和文档研究表明,RefCountingDataReader
,即 IDataReader
的默认实现,有一个名为 InnerDataReader
的属性,引用 Enterprise Library 5.0 文档的话来说,“我们正在包装的实际原始 IDataReader
”。InnerDataReader
可以毫无问题地转换为 SqlDataReader
。因此,我们需要首先将从 ExecuteReader()
方法调用返回的 IDataReader
转换为 RefCountingDataReader
,然后将 RefCountingDataReader
的 InnerDataReader
转换为 SqlDataReader
。这是一个两步转换,如 DataObject
类中的 CastToSqlDataReader(IDataReader)
方法所示。
public static SqlDataReader CastToSqlDataReader(IDataReader iReader)
{
//Cast IDataReader to RefCountingDataReader first,
//and then cast the innerReader of the RefCountingDataReader to SqlDataReader
return (SqlDataReader)((RefCountingDataReader)iReader).InnerReader;
}
在 default.aspx 页面上,我们再次先获取 IDataReader
,然后将 IDataReader
传递给 DataObject.CastToSqlDataReader()
方法,以便正确地将 IDataReader
转换为 SqlDataReader
,然后将其分配给 GridView3
作为其数据源。
using (IDataReader IReader = DataObject.GetIDataReader(ConnectionString, "select * from Customers"))
{
GridView3.DataSource = DataObject.CastToSqlDataReader(IReader);
GridView3.DataBind();
}
以下是结果的截图:
它奏效了。需要注意的是,将原始 IDataReader
包装在 using{}
块中而不是包装 InnerDataReader
是很重要的。这是为了确保数据库连接得到妥善管理,因为仅关闭 InnerDataReader
并不能释放外部 IDataReader
持有的连接。
使用扩展方法
上述实现工作正常。但是我们可以做得更优雅。在 ASP.NET 中,我认为最常用的方法是:
SomeObject.ToString()
我们可以为 IDataReader
转换做类似的事情,例如 IDataReader.ToSqlDataReader()
吗?是的,我们可以,利用扩展方法。MSDN 上有很多关于扩展方法的资料,您可以自行探索。在我们的例子中,在 DataAccessEntLib
命名空间中编写一个非常简单的静态方法,如此处所示:
public static class DataAccessEntLibExtension
{
public static SqlDataReader ToSqlDataReader(this IDataReader reader)
{
return (SqlDataReader)((RefCountingDataReader)reader).InnerReader;
}
}
this
关键字,加上方法中 IDataReader
的参数,使得该方法成为 IDataReader
的成员,前提是在 default.aspx 代码页面中添加了 DataAccessEntLib
命名空间的 using
指令。有了扩展方法,我们就可以使用如下代码将数据绑定到页面上的 GridView4
:
using (IDataReader IReader = DataObject.GetIDataReader(
ConnectionString, "select * from Customers"))
{
GridView4.DataSource = IReader.ToSqlDataReader();
GridView4.DataBind();
}
再次,它奏效了。我们在页面上显示了结果,如下图所示:
摘要
尚不清楚本文介绍的 Enterprise Library 5.0 中 IDataReader
的行为是设计使然还是偶然。我们的上述实验表明,如果应用程序在循环中直接从 IDataReader
读取数据是可以的。然而,如果应用程序需要将 IDataReader
绑定到 ASP.NET 数据服务器控件,这里介绍的技巧可能是必须使用的。在我看来,.NET 数据服务器控件应该能够直接将 IDataReader
用作其数据源,而无需任何修改。否则就失去了意义。
发现这样一个小问题的解决方案是一件有趣的事情。