使用 DataReader 检索数据
若要使用 DataReader 检索数据,请创建 Command 对象的实例,然后通过调用 Command.ExecuteReader 创建一个 DataReader,以便从数据源检索行 。 DataReader 提供未缓冲的数据流,该数据流使过程逻辑可以有效地按顺序处理从数据源中返回的结果。 由于数据不在内存中缓存,所以在检索大量数据时,DataReader 是一种适合的选择。
下面的示例演示如何使用 DataReader,其中 reader
表示有效的 DataReader,而 command
表示有效的 Command 对象。
reader = command.ExecuteReader();
reader = command.ExecuteReader()
使用 DataReader.Read 方法从查询结果中获取行。 通过向 DataReader 传递列的名称或序号,可以访问返回行的每一列。 不过,为了实现最佳性能,DataReader 提供了一系列方法,将使你能够访问其本机数据类型(GetDateTime、GetDouble、GetGuid、GetInt32 等)的列值 。 有关特定于数据提供程序的 DataReaders 的类型化访问器方法列表,请参见 OleDbDataReader 和 SqlDataReader。 已知基础数据类型时,如果使用类型化访问器方法,将减少在检索列值时所需的类型转换量。
以下示例循环访问一个 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("{0}\t{1}", reader.GetInt32(0),
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 打开时,该 DataReader 将以独占方式使用 Connection 。 在原始 DataReader 关闭之前,将无法对 Connection 执行任何命令(包括创建另一个 DataReader) 。
备注
不要在类的 Finalize 方法中对 Connection、DataReader 或任何其他托管对象调用 Close 或 Dispose 。 在终结器中,仅释放类直接拥有的非托管资源。 如果类不拥有任何非托管资源,则不要在类定义中包含 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{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();
}
}
}
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 中获取架构信息
当 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 类型 adChapter)可以使用 OleDbDataReader 来检索。 当以 DataReader 的形式返回包含某章节的查询时,该章节将以此 DataReader 中列的形式返回,并作为 DataReader 对象公开。
ADO.NET DataSet 也可用于通过表间的父子关系来表示分层行集。 有关详细信息,请参阅 数据集、数据表和数据视图。
以下代码示例使用 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 的形式返回。
你可以使用 ExecuteReader 方法检索表示 ORACLE REF CURSOR 的 OracleDataReader 对象。 还可以针对用来填充 DataSet 的 OracleDataAdapter 指定会返回一个或多个 ORACLE REF CURSOR 的 OracleCommand 作为 SelectCommand。
若要访问从 Oracle 数据源中返回的 REF CURSOR,请为查询创建 OracleCommand,并将引用 REF CURSOR 的输出参数添加到 OracleCommand 的 Parameters 集合中。 该参数的名称必须与查询中的 REF CURSOR 参数名称相匹配。 将参数类型设置为 OracleType.Cursor。 OracleCommand 的 OracleCommand.ExecuteReader() 方法会为 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;
下面的代码将创建一个 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;
下面的代码使用 OracleDataReader 的 Read() 和 NextResult() 方法返回上一个命令的结果。 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 事件。