65.9K
CodeProject 正在变化。 阅读更多。
Home

Enterprise Library 5.0 数据访问应用程序块:玩转 IDataReader

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (6投票s)

2012 年 1 月 12 日

CPOL

7分钟阅读

viewsIcon

69379

downloadIcon

2891

探索 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 服务器控件显示数据。输出如图下截图所示。

a_ReadIDataReader.JPG

它奏效了。正如您所见,应用程序能够像读取 SqlDataReader 中的数据一样,从 IDataReader 中读取数据。代码包含在一个 using{} 块中,以确保在到达数据末尾时 IDataReader 会被自动关闭。

将 IDataReader 绑定到 GridView

到目前为止一切顺利。我们能够从 IDataReader 读取数据。但是我们能将 IDataReader 绑定到 GridView 服务器控件吗?以下是 Enterprise Library 5.0 文档中的描述和代码示例:

doc_usingReader.JPG

从文档来看,我们似乎可以做到。让我们尝试一下,看看结果如何。在我们的 default.aspx 页面中,下面的代码用于将 IDataReader 绑定到 GridView

using (IDataReader IReader = DataObject.GetIDataReader(ConnectionString, 
                   "select * from Customers"))
{
 GridView1.DataSource = IReader;
 GridView1.DataBind();
}

再次调用 DataObject.GetIDataReader() 方法获取 IDataReader,然后将其分配给页面上的 GridView1 作为其数据源。执行后,我们没有看到结果,而是看到了一个错误消息:

b_BindIDataReader.JPG

它不起作用。错误表明 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();
}

我们又收到了一个错误消息,与之前的不同:

c_CastToSqlDataReader.JPG

它不起作用。实际上,这个错误消息相当令人困惑,因为出现了一种新的数据读取器类型 RefCountingDataReader,而我们以为我们正在尝试将 IDataReader 转换为 SqlDataReader。您会想这个读取器是从哪里来的。根据 MSDN 文档,事实证明 RefCountingDataReaderIDataReader 接口的默认实现。因此,我们需要深入研究 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,然后将 RefCountingDataReaderInnerDataReader 转换为 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();
}

以下是结果的截图:

d_ConvertToSqlDataReader.JPG

它奏效了。需要注意的是,将原始 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();
}

再次,它奏效了。我们在页面上显示了结果,如下图所示:

e_ExtensionMethod.JPG

摘要

尚不清楚本文介绍的 Enterprise Library 5.0 中 IDataReader 的行为是设计使然还是偶然。我们的上述实验表明,如果应用程序在循环中直接从 IDataReader 读取数据是可以的。然而,如果应用程序需要将 IDataReader 绑定到 ASP.NET 数据服务器控件,这里介绍的技巧可能是必须使用的。在我看来,.NET 数据服务器控件应该能够直接将 IDataReader 用作其数据源,而无需任何修改。否则就失去了意义。

发现这样一个小问题的解决方案是一件有趣的事情。

参考

© . All rights reserved.