使用 DataReader 检索数据

若要使用 DataReader 检索数据,请创建对象的实例Command,然后通过调用 DataReader 从数据源检索行来创建。 提供 DataReader 一个无缓冲区数据流,使过程逻辑能够按顺序高效地处理数据源的结果。 DataReader 是检索大量数据时的一个不错选择,因为数据未缓存在内存中。

下面的示例演示如何使用 DataReader,其中 表示有效的 DataReader,而 reader 表示有效的 Command 对象。

reader = command.ExecuteReader();
reader = command.ExecuteReader()

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

以下示例循环访问对象 DataReader ,并返回每行中的两列。

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

        SqlDataReader reader = command.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine($"{reader.GetInt32(0)}\t{reader.GetString(1)}");
            }
        }
        else
        {
            Console.WriteLine("No rows found.");
        }
        reader.Close();
    }
}
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

关闭 DataReader

在使用完DataReader对象后,始终调用Close方法。

如果Command包含输出参数或返回值,则这些值只有在DataReader关闭后才可用。

DataReader 打开时,Connection 专门供该 DataReader 使用。 在关闭原始之前,无法为连接执行任何命令,包括创建另一个DataReader

注释

请勿在类的方法中对CloseDisposeConnectionDataReader或任何其他托管对象进行调用。 在终结器中,仅释放类直接拥有的非托管资源。 如果类不拥有任何非托管资源,请不要在类定义中包含方法 Finalize 。 有关详细信息,请参阅垃圾回收

使用 NextResult 检索多个结果集

DataReader如果返回多个结果集,请调用NextResult该方法以按顺序循环访问结果集。 以下示例显示 SqlDataReader 如何使用 ExecuteReader 方法处理两个 SELECT 语句的结果。

static void RetrieveMultipleResults(SqlConnection connection)
{
    using (connection)
    {
        SqlCommand command = new(
          "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{reader.GetName(0)}\t{reader.GetName(1)}");

            while (reader.Read())
            {
                Console.WriteLine($"\t{reader.GetInt32(0)}\t{reader.GetString(1)}");
            }
            reader.NextResult();
        }
    }
}
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

从 DataReader 获取架构信息

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

static void GetSchemaInfo(SqlConnection connection)
{
    using (connection)
    {
        SqlCommand command = new(
          "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]));
            }
        }
    }
}
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

使用 OLE DB 章节

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

ADO.NET DataSet 还可用于使用表之间的父子关系来表示分层行集。 有关详细信息,请参阅 DataSets、DataTables 和 DataViews

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

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

    Using 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()

        Using custReader As OleDbDataReader = custCMD.ExecuteReader()

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

                Using orderReader As OleDbDataReader = 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()
                End Using
            Loop
            ' Make sure to always close readers and connections.
            custReader.Close()
        End Using
    End Using
End Using
using (OleDbConnection connection = new OleDbConnection(
    "Provider=MSDataShape;Data Provider=SQLOLEDB;" +
    "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind"))
{
    using (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();

        using (OleDbDataReader custReader = custCMD.ExecuteReader())
        {

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

                using (OleDbDataReader 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 的形式返回。

可以通过使用OracleDataReader方法来检索表示 Oracle REF CURSOR 的ExecuteReader对象。 还可以指定一个OracleCommand返回一个或多个 Oracle REF CURSOR 作为SelectCommandOracleDataAdapter用于填充DataSet

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

如果OracleCommand返回多个 REF CURSORS,请添加多个输出参数。 可以通过调用 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;

下面的代码将创建一个 OracleCommand,通过将类型 OracleType.Cursor 的两个参数添加到 OracleCommand.Parameters 集合中,从以前的 ORACLE 包返回 REF CURSOR。

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;

以下代码使用Read()NextResult()OracleDataReader方法返回上一个命令的结果。 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

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);

注释

为了避免 OverflowException,建议在将值存储在一个 DataRow中之前,还要处理从 Oracle NUMBER 类型到有效的 .NET Framework 类型的任何转换。 可以使用该 FillError 事件来确定 OverflowException 是否已发生。 有关 FillError 事件的详细信息,请参阅 处理 DataAdapter 事件

另请参阅