Recuperar datos mediante DataReader

Puede usar DataReader de ADO.NET para recuperar una secuencia de datos de sólo avance y de sólo lectura desde una base de datos. Los resultados se devuelven cuando se ejecuta la consulta y se almacenan en el búfer de red del cliente hasta que el usuario los solicita mediante el método Read del objeto DataReader. El uso de DataReader puede mejorar el rendimiento de la aplicación mediante la recuperación de los datos tan pronto como estén disponibles, en lugar de tener que esperar a que se devuelvan todos los resultados de la consulta, y (de forma predeterminada) mediante el almacenamiento de una sola fila cada vez en memoria, lo que reduce la sobrecarga del sistema.

Después de crear una instancia del objeto Command, para crear un DataReader debe llamar a Command.ExecuteReader con el fin de recuperar las filas desde un origen de datos, como se muestra en el ejemplo siguiente.

Dim myReader As SqlDataReader = myCommand.ExecuteReader()
[C#]
SqlDataReader myReader = myCommand.ExecuteReader();

Puede usar el método Read del objeto DataReader para obtener una fila a partir de los resultados de una consulta. Para tener acceso a cada columna de la fila devuelta, puede pasar a DataReader el nombre o referencia numérica de la columna en cuestión. Sin embargo, el mejor rendimiento se logra con los métodos que ofrece DataReader y que permiten tener acceso a los valores de las columnas en sus tipos de datos nativos (GetDateTime, GetDouble, GetGuid, GetInt32, etcétera). Para obtener una lista de métodos de descriptor de acceso con tipo, vea OleDbDataReader (Clase) y SqlDataReader (Clase). Si se usan los métodos de descriptor de acceso con tipo cuando se conoce el tipo de datos subyacente, se reduce el número de conversiones de tipo necesarias para recuperar el valor de una columna.

Nota   La versión de Windows Server 2003 de .NET Framework incluye una propiedad adicional para DataReader, HasRows, que permite determinar si DataReader ha devuelto resultados antes de la lectura del mismo.

En el ejemplo de código siguiente se itera por un objeto DataReader y se devuelven dos columnas de cada fila.

If myReader.HasRows Then
  Do While myReader.Read()
    Console.WriteLine(vbTab & "{0}" & vbTab & "{1}", myReader.GetInt32(0), myReader.GetString(1))
  Loop
Else
  Console.WriteLine("No rows returned.")
End If

myReader.Close()
[C#]
if (myReader.HasRows)
  while (myReader.Read())
    Console.WriteLine("\t{0}\t{1}", myReader.GetInt32(0), myReader.GetString(1));
else
  Console.WriteLine("No rows returned.");

myReader.Close();

DataReader proporciona una secuencia de datos sin búfer que permite a la lógica de los procedimientos procesar eficazmente y de forma secuencial los resultados procedentes de un origen de datos. DataReader es la mejor opción cuando se trata de recuperar grandes cantidades de datos, ya que éstos no se almacenan en la memoria caché.

Cerrar el DataReader

Siempre debe llamar al método Close cuando haya terminado de usar el objeto DataReader.

Si Command contiene parámetros de salida o valores devueltos, éstos no estarán disponibles hasta que se cierre el DataReader.

Tenga en cuenta que mientras está abierto un DataReader, éste usa de forma exclusiva el objeto Connection. Por eso no podrá ejecutar ningún comando en el objeto Connection, ni siquiera el de creación de otro DataReader, hasta que se cierre el DataReader original.

Nota   No llame a Close o Dispose en un objeto Connection, DataReader, o cualquier otro objeto administrado en el método Finalize de su clase. En un finalizador, libere sólo los recursos no administrados que pertenezcan directamente a su clase. Si la clase no dispone de recursos no administrados, no incluya un método Finalize en la definición de clase. Para obtener más información, vea Programar para la recolección de elementos no utilizados.

Múltiples conjuntos de resultados

Si se devuelven varios conjuntos de resultados, DataReader proporciona el método NextResult, que permite recorrer en iteración los conjuntos de resultados, como se muestra en el ejemplo siguiente.

Dim myCMD As SqlCommand = New SqlCommand("SELECT CategoryID, CategoryName FROM Categories;" & _
                                         "SELECT EmployeeID, LastName FROM Employees", nwindConn)
nwindConn.Open()

Dim myReader As SqlDataReader = myCMD.ExecuteReader()

Dim fNextResult As Boolean = True
Do Until Not fNextResult
  Console.WriteLine(vbTab & myReader.GetName(0) & vbTab & myReader.GetName(1))

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

  fNextResult = myReader.NextResult()
Loop

myReader.Close()
nwindConn.Close()
[C#]
SqlCommand myCMD = new SqlCommand("SELECT CategoryID, CategoryName FROM Categories;" +
                                  "SELECT EmployeeID, LastName FROM Employees", nwindConn);
nwindConn.Open();

SqlDataReader myReader = myCMD.ExecuteReader();

do
{
  Console.WriteLine("\t{0}\t{1}", myReader.GetName(0), myReader.GetName(1));

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

} while (myReader.NextResult());

myReader.Close();
nwindConn.Close();

Obtener información del esquema a partir del DataReader

Mientras hay abierto un DataReader, puede usar el método GetSchemaTable para recuperar información del esquema acerca del conjunto actual de resultados. GetSchemaTable devuelve un objeto DataTable lleno con filas y columnas que contienen la información del esquema del conjunto actual de resultados. DataTable contiene una fila por cada una de las columnas del conjunto de resultados. Cada columna de una fila de la tabla de esquema está asociada a una propiedad de la columna que se devuelve en el conjunto de resultados. ColumnName es el nombre de la propiedad y el valor de la columna es el de la propiedad. En el ejemplo de código siguiente se muestra la información del esquema de DataReader.

Dim schemaTable As DataTable = myReader.GetSchemaTable()

Dim myRow As DataRow
Dim myCol As DataColumn

For Each myRow In schemaTable.Rows
  For Each myCol In schemaTable.Columns
    Console.WriteLine(myCol.ColumnName & " = " & myRow(myCol).ToString())
  Next
  Console.WriteLine()
Next
[C#]
DataTable schemaTable = myReader.GetSchemaTable();

foreach (DataRow myRow in schemaTable.Rows)
{
  foreach (DataColumn myCol in schemaTable.Columns)
    Console.WriteLine(myCol.ColumnName + " = " + myRow[myCol]);
  Console.WriteLine();
}

Capítulos de OLE DB

Mediante OleDbDataReader puede recuperar conjuntos jerárquicos de filas, o capítulos (tipo DBTYPE_HCHAPTER de OLE DB y tipo adChapter de ADO). Cuando se devuelve en forma de DataReader una consulta que incluye un capítulo, éste se devuelve como una columna del DataReader y se expone como un objeto DataReader.

También se puede usar DataSet de ADO.NET para representar conjuntos jerárquicos de filas utilizando relaciones entre tablas primarias y secundarias. Para obtener más información, vea Crear y utilizar DataSets.

En el ejemplo de código siguiente se utiliza el proveedor MSDataShape para generar un capítulo con la columna de pedidos realizados por cada uno de los clientes de una lista.

Dim nwindConn 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)", nwindConn)
nwindConn.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

custReader.Close()
nwindConn.Close()
[C#]
OleDbConnection nwindConn = 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)", nwindConn);
nwindConn.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();
}

custReader.Close();
nwindConn.Close();

Cursores REF CURSOR de Oracle

El proveedor de datos de .NET Framework para Oracle admite el uso de cursores REF CURSOR de Oracle para devolver los resultados de una consulta. Un REF CURSOR de Oracle se devuelve en forma de objeto OracleDataReader.

Puede recuperar un objeto OracleDataReader, que representa un REF CURSOR de Oracle, mediante el método OracleCommand.ExecuteReader. También puede especificar un OracleCommand que devuelva uno o varios cursores REF CURSOR de Oracle en forma del SelectCommand de un OracleDataAdapter utilizado para rellenar un DataSet.

Para obtener acceso a un REF CURSOR devuelto desde un origen de datos de Oracle, cree un OracleCommand para la consulta y agregue un parámetro de salida que establezca una referencia entre el REF CURSOR y la colección Parameters de OracleCommand. El nombre del parámetro debe coincidir con el nombre del parámetro REF CURSOR de la consulta. Establezca el tipo de parámetro en OracleType.Cursor. El método ExecuteReader de OracleCommand devolverá un objeto OracleDataReader para el REF CURSOR.

Si OracleCommand devuelve varios cursores REF CURSOR, agregue varios parámetros de salida. Puede obtener acceso a los distintos cursores REF CURSOR llamando al método OracleCommand.ExecuteReader. La llamada a ExecuteReader devolverá un objeto OracleDataReader que haga referencia al primer REF CURSOR. A continuación, puede llamar al método OracleDataReader.NextResult para obtener acceso a los cursores REF CURSOR posteriores. Aunque los parámetros de la colección OracleCommand.Parameters tengan el mismo nombre que los parámetros de salida de REF CURSOR, OracleDataReader obtendrá acceso a éstos en el mismo orden en el que se agregaron a la colección Parameters.

Por ejemplo, considere el siguiente paquete de Oracle y, concretamente, el cuerpo del paquete.

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; 

En el código siguiente se crea un OracleCommand que devuelve los cursores REF CURSOR del paquete anterior de Oracle mediante la adición de dos parámetros de tipo OracleType.Cursor a la colección 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
[C#]
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;

El código siguiente devuelve los resultados del comando anterior utilizando los métodos Read y NextResult del OracleDataReader. Los parámetros REF CURSOR se devuelven en orden.

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 rdr As OracleDataReader = cursCmd.ExecuteReader()

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

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

rdr.NextResult()

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

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

rdr.Close()
oraConn.Close()
[C#]
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 rdr = cursCmd.ExecuteReader();

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

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

rdr.NextResult();

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

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

rdr.Close();
oraConn.Close();

En el siguiente ejemplo se utiliza el comando anterior para rellenar un DataSet con los resultados del paquete de Oracle.

**Nota   **Se recomienda que el usuario controle también cualquier conversión del tipo NUMBER de Oracle a un tipo válido de .NET Framework antes de almacenar el valor en DataRow para evitar que se produzca una excepción OverflowException. Puede utilizar el evento FillError para determinar si se ha producido una excepción OverflowException. Para obtener más información acerca del evento FillError, vea Trabajar con eventos DataAdapter.

Dim ds As DataSet = New DataSet()

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

oraDa.Fill(ds)
[C#]
DataSet ds = new DataSet();

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

oraDa.Fill(ds);

Vea también

Utilizar proveedores de datos de .NET Framework para obtener acceso a datos | DataTable (Clase) | OleDbDataReader (Clase) | SqlDataReader (Clase)