使用 DataReader 检索数据 (ADO.NET)

使用 DataReader 检索数据包括创建 Command 对象的实例,然后通过调用 Command.ExecuteReader 创建一个 DataReader,以便从数据源检索行。 下面的示例演示如何使用 DataReader,其中 reader 表示有效的 DataReader,而 command 表示有效的 Command 对象。

reader = command.ExecuteReader();

使用 DataReader 对象的 Read 方法可从查询结果中获取行。 通过向 DataReader 传递列的名称或序号引用,可以访问返回行的每一列。 不过,为了实现最佳性能,DataReader 提供了一系列方法,将使您能够访问其本机数据类型(GetDateTimeGetDoubleGetGuidGetInt32 等)的列值。 有关数据提供程序特定的 DataReaders 的类型化访问器方法列表,请参见 OleDbDataReaderSqlDataReader。 假定基础数据类型为已知,如果使用类型化访问器方法,将减少在检索列值时所需的类型转换量。

注意注意

.NET Framework 的 Windows Server 2003 版包含 DataReader 的附加属性 HasRows,该属性使您能够在读取 DataReader 之前就可确定它是否返回了任何结果。

以下代码示例循环访问一个 DataReader 对象,并从每个行中返回两个列。

Private Sub HasRows(ByVal connection As SqlConnection)
    Using connection
        Dim command As SqlCommand = New SqlCommand( _
          "SELECT CategoryID, CategoryName FROM Categories;", _
          connection)
        connection.Open()

        Dim reader As SqlDataReader = command.ExecuteReader()

        If reader.HasRows Then
            Do While reader.Read()
                Console.WriteLine(reader.GetInt32(0) _
                  & vbTab & reader.GetString(1))
            Loop
        Else
            Console.WriteLine("No rows found.")
        End If

        reader.Close()
    End Using
End Sub
static void HasRows(SqlConnection connection)
{
    using (connection)
    {
        SqlCommand command = new SqlCommand(
          "SELECT CategoryID, CategoryName FROM Categories;",
          connection);
        connection.Open();

        SqlDataReader reader = command.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine("{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
        }
        else
        {
            Console.WriteLine("No rows found.");
        }
        reader.Close();
    }
}

DataReader 提供未缓冲的数据流,该数据流使过程逻辑可以有效地按顺序处理从数据源中返回的结果。 由于数据不在内存中缓存,所以在检索大量数据时,DataReader 是一种适合的选择。

关闭 DataReader

每次使用完 DataReader 对象后都应调用 Close 方法。

如果 Command 包含输出参数或返回值,那么在 DataReader 关闭之前,将无法访问这些输出参数或返回值。

请注意,当 DataReader 打开时,该 DataReader 将以独占方式使用 Connection。 在原始 DataReader 关闭之前,将无法对 Connection 执行任何命令(包括创建另一个 DataReader)。

注意注意

不要在类的 Finalize 方法中对 ConnectionDataReader 或任何其他托管对象调用 CloseDispose。在终结器中,仅释放类直接拥有的非托管资源。如果类不拥有任何非托管资源,则不要在类定义中包含 Finalize 方法。有关更多信息,请参见 垃圾回收

使用 NextResult 检索多个结果集

如果返回的是多个结果集,DataReader 会提供 NextResult 方法来按顺序循环访问这些结果集。 以下示例显示 SqlDataReader 如何使用 ExecuteReader 方法处理两个 SELECT 语句的结果。

Private Sub RetrieveMultipleResults(ByVal connection As SqlConnection)
    Using connection
        Dim command As SqlCommand = New SqlCommand( _
          "SELECT CategoryID, CategoryName FROM Categories;" & _
          "SELECT EmployeeID, LastName FROM Employees", connection)
        connection.Open()

        Dim reader As SqlDataReader = command.ExecuteReader()

        Do While reader.HasRows
            Console.WriteLine(vbTab & reader.GetName(0) _
              & vbTab & reader.GetName(1))

            Do While reader.Read()
                Console.WriteLine(vbTab & reader.GetInt32(0) _
                  & vbTab & reader.GetString(1))
            Loop

            reader.NextResult()
        Loop
    End Using
End Sub
static void RetrieveMultipleResults(SqlConnection connection)
{
    using (connection)
    {
        SqlCommand command = new SqlCommand(
          "SELECT CategoryID, CategoryName FROM dbo.Categories;" +
          "SELECT EmployeeID, LastName FROM dbo.Employees",
          connection);
        connection.Open();

        SqlDataReader reader = command.ExecuteReader();

        while (reader.HasRows)
        {
            Console.WriteLine("\t{0}\t{1}", reader.GetName(0),
                reader.GetName(1));

            while (reader.Read())
            {
                Console.WriteLine("\t{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
            reader.NextResult();
        }
    }
}

从 DataReader 中获取架构信息

DataReader 打开时,可以使用 GetSchemaTable 方法检索有关当前结果集的架构信息。 GetSchemaTable 将返回一个填充了行和列的 DataTable 对象,这些行和列包含当前结果集的架构信息。 对于结果集的每一列,DataTable 都包含一行。 架构表行的每一列都映射到在结果集中返回的列的属性,其中 ColumnName 是属性的名称,而列的值为属性的值。 以下代码示例为 DataReader 编写架构信息。

Private Sub GetSchemaInfo(ByVal connection As SqlConnection)
    Using connection
        Dim command As SqlCommand = New SqlCommand( _
          "SELECT CategoryID, CategoryName FROM Categories;", _
          connection)
        connection.Open()

        Dim reader As SqlDataReader = command.ExecuteReader()
        Dim schemaTable As DataTable = reader.GetSchemaTable()

        Dim row As DataRow
        Dim column As DataColumn

        For Each row In schemaTable.Rows
            For Each column In schemaTable.Columns
                Console.WriteLine(String.Format("{0} = {1}", _
                  column.ColumnName, row(column)))
            Next
            Console.WriteLine()
        Next
        reader.Close()
    End Using
End Sub
static void GetSchemaInfo(SqlConnection connection)
{
    using (connection)
    {
        SqlCommand command = new SqlCommand(
          "SELECT CategoryID, CategoryName FROM Categories;",
          connection);
        connection.Open();

        SqlDataReader reader = command.ExecuteReader();
        DataTable schemaTable = reader.GetSchemaTable();

        foreach (DataRow row in schemaTable.Rows)
        {
            foreach (DataColumn column in schemaTable.Columns)
            {
                Console.WriteLine(String.Format("{0} = {1}",
                   column.ColumnName, row[column]));
            }
        }
    }
}

使用 OLE DB 章节

分层行集或章节(OLE DB 类型 DBTYPE_HCHAPTER、ADO 类型 adChapter)可以使用 OleDbDataReader 来检索。 当以 DataReader 的形式返回包含某章节的查询时,该章节将以此 DataReader 中列的形式返回,并作为 DataReader 对象公开。

ADO.NET DataSet 也可用于通过表间的父子关系来表示分层行集。 有关更多信息,请参见 DataSet、DataTable 和 DataView (ADO.NET)

以下代码示例使用 MSDataShape 提供程序来为客户列表中的每个客户生成订单的章节列。

Using connection As OleDbConnection = New OleDbConnection( _
  "Provider=MSDataShape;Data Provider=SQLOLEDB;" & _
  "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

Dim custCMD As OleDbCommand = New OleDbCommand( _
  "SHAPE {SELECT CustomerID, CompanyName FROM Customers} " & _
  "APPEND ({SELECT CustomerID, OrderID FROM Orders} AS CustomerOrders " & _
  "RELATE CustomerID TO CustomerID)", connection)
connection.Open()

Dim custReader As OleDbDataReader = custCMD.ExecuteReader()
Dim orderReader As OleDbDataReader

Do While custReader.Read()
  Console.WriteLine("Orders for " & custReader.GetString(1)) 
  ' custReader.GetString(1) = CompanyName

  orderReader = custReader.GetValue(2)
  ' custReader.GetValue(2) = Orders chapter as DataReader

  Do While orderReader.Read()
    Console.WriteLine(vbTab & orderReader.GetInt32(1))
    ' orderReader.GetInt32(1) = OrderID
  Loop
  orderReader.Close()
Loop
' Make sure to always close readers and connections.
custReader.Close()
End Using
Using (OleDbConnection connection = new OleDbConnection(
  "Provider=MSDataShape;Data Provider=SQLOLEDB;" +
  "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind"));
{
OleDbCommand custCMD = new OleDbCommand(
  "SHAPE {SELECT CustomerID, CompanyName FROM Customers} " +
  "APPEND ({SELECT CustomerID, OrderID FROM Orders} AS CustomerOrders " +
  "RELATE CustomerID TO CustomerID)", connection);
connection.Open();

OleDbDataReader custReader = custCMD.ExecuteReader();
OleDbDataReader orderReader;

while (custReader.Read())
{
  Console.WriteLine("Orders for " + custReader.GetString(1)); 
  // custReader.GetString(1) = CompanyName

  orderReader = (OleDbDataReader)custReader.GetValue(2);      
  // custReader.GetValue(2) = Orders chapter as DataReader

  while (orderReader.Read())
    Console.WriteLine("\t" + orderReader.GetInt32(1));        
    // orderReader.GetInt32(1) = OrderID
  orderReader.Close();
}
// Make sure to always close readers and connections.
custReader.Close();
}

使用 Oracle REF CURSOR 返回结果

Oracle .NET Framework 数据提供程序支持使用 Oracle REF CURSOR 返回查询结果。 Oracle REF CURSOR 以 OracleDataReader 的形式返回。

可以使用 ExecuteReader 方法检索表示 Oracle REF CURSOR 的 OracleDataReader 对象,也可以将返回一个或多个 Oracle REF CURSOR 的 OracleCommand 指定为用于填充 DataSetOracleDataAdapterSelectCommand

若要访问从 Oracle 数据源中返回的 REF CURSOR,请为查询创建 OracleCommand,并将引用 REF CURSOR 的输出参数添加到 OracleCommandParameters 集合中。 该参数的名称必须与查询中的 REF CURSOR 参数名称相匹配。 将参数类型设置为 OracleType.CursorOracleCommandExecuteReader 方法将为 REF CURSOR 返回 OracleDataReader

如果 OracleCommand 返回多个 REF CURSOR,则将添加多个输出参数。 通过调用 OracleCommand.ExecuteReader 方法可以访问不同的 REF CURSOR。 调用 ExecuteReader 将返回引用第一个 REF CURSOR 的 OracleDataReader。 然后,可以调用 OracleDataReader.NextResult 方法访问随后的 REF CURSOR。 尽管 OracleCommand.Parameters 集合中参数的名称与 REF CURSOR 输出参数名称相匹配,但 OracleDataReader 将按这些参数添加到 Parameters 集合中的顺序进行访问。

例如,请看下面这个 Oracle 包和包正文。

CREATE OR REPLACE PACKAGE CURSPKG AS 
  TYPE T_CURSOR IS REF CURSOR; 
  PROCEDURE OPEN_TWO_CURSORS (EMPCURSOR OUT T_CURSOR, 
    DEPTCURSOR OUT T_CURSOR); 
END CURSPKG;

CREATE OR REPLACE PACKAGE BODY CURSPKG AS 
  PROCEDURE OPEN_TWO_CURSORS (EMPCURSOR OUT T_CURSOR, 
    DEPTCURSOR OUT T_CURSOR) 
  IS 
  BEGIN 
    OPEN EMPCURSOR FOR SELECT * FROM DEMO.EMPLOYEE; 
    OPEN DEPTCURSOR FOR SELECT * FROM DEMO.DEPARTMENT; 
  END OPEN_TWO_CURSORS; 
END CURSPKG; 

以下代码将创建从先前的 Oracle 包返回 REF CURSOR 的 OracleCommand,方法是将类型 OracleType.Cursor 的两个参数添加到 Parameters 集合中。

Dim cursCmd As OracleCommand = New OracleCommand("CURSPKG.OPEN_TWO_CURSORS", oraConn)
cursCmd.Parameters.Add("EMPCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output
cursCmd.Parameters.Add("DEPTCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output
OracleCommand cursCmd = new OracleCommand("CURSPKG.OPEN_TWO_CURSORS", oraConn);
cursCmd.Parameters.Add("EMPCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output;
cursCmd.Parameters.Add("DEPTCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output;

以下代码使用 OracleDataReaderReadNextResult 方法返回先前命令的结果。 REF CURSOR 参数按顺序返回。

oraConn.Open()

Dim cursCmd As OracleCommand = New OracleCommand("CURSPKG.OPEN_TWO_CURSORS", oraConn)
cursCmd.CommandType = CommandType.StoredProcedure
cursCmd.Parameters.Add("EMPCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output
cursCmd.Parameters.Add("DEPTCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output

Dim reader As OracleDataReader = cursCmd.ExecuteReader()

Console.WriteLine(vbCrLf & "Emp ID" & vbTab & "Name")

Do While reader.Read()
  Console.WriteLine("{0}" & vbTab & "{1}, {2}", reader.GetOracleNumber(0), reader.GetString(1), reader.GetString(2))
Loop

reader.NextResult()

Console.WriteLine(vbCrLf & "Dept ID" & vbTab & "Name")

Do While reader.Read()
  Console.WriteLine("{0}" & vbTab & "{1}", reader.GetOracleNumber(0), reader.GetString(1))
Loop
' Make sure to always close readers and connections.
reader.Close()
oraConn.Close()
oraConn.Open();

OracleCommand cursCmd = new OracleCommand("CURSPKG.OPEN_TWO_CURSORS", oraConn);
cursCmd.CommandType = CommandType.StoredProcedure;
cursCmd.Parameters.Add("EMPCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output;
cursCmd.Parameters.Add("DEPTCURSOR", OracleType.Cursor).Direction = ParameterDirection.Output;

OracleDataReader reader = cursCmd.ExecuteReader();

Console.WriteLine("\nEmp ID\tName");

while (reader.Read())
  Console.WriteLine("{0}\t{1}, {2}", reader.GetOracleNumber(0), reader.GetString(1), reader.GetString(2));

reader.NextResult();

Console.WriteLine("\nDept ID\tName");

while (reader.Read())
  Console.WriteLine("{0}\t{1}", reader.GetOracleNumber(0), reader.GetString(1));
// Make sure to always close readers and connections.
reader.Close();
oraConn.Close();

以下示例使用先前的命令用 Oracle 包的结果来填充 DataSet

注意注意

为了避免 OverflowException,建议您在将值存储在 DataRow 中之前,还应执行从 Oracle NUMBER 类型到有效的 .NET Framework 类型的任何转换。可以使用 FillError 事件来确定是否发生了 OverflowException。有关 FillError 事件的更多信息,请参见 处理 DataAdapter 事件 (ADO.NET)

Dim ds As DataSet = New DataSet()

Dim adapter As OracleDataAdapter = New OracleDataAdapter(cursCmd)
adapter.TableMappings.Add("Table", "Employees")
adapter.TableMappings.Add("Table1", "Departments")

adapter.Fill(ds)
DataSet ds = new DataSet();

OracleDataAdapter adapter = new OracleDataAdapter(cursCmd);
adapter.TableMappings.Add("Table", "Employees");
adapter.TableMappings.Add("Table1", "Departments");

adapter.Fill(ds);

请参见

其他资源

Working with DataReaders

DataAdapter 和 DataReader (ADO.NET)

命令和参数 (ADO.NET)

检索数据库架构信息 (ADO.NET)