Recupero di dati mediante DataReader

È possibile utilizzare DataReader di ADO.NET per recuperare un flusso di dati di sola lettura forward-only da un database. I risultati vengono restituiti all'esecuzione della query e vengono memorizzati nel buffer di rete del client fino a quando li si richiede utilizzando il metodo Read di DataReader. L'utilizzo di DataReader consente di migliorare le prestazioni dell'applicazione perché permette di recuperare i dati appena sono disponibili, senza attendere la restituzione del risultato dell'intera query, e perché la collocazione in memoria di una sola riga per volta (impostazione predefinita) riduce l'overhead del sistema.

Una volta creata un'istanza dell'oggetto Command, creare un DataReader chiamando Command.ExecuteReader per recuperare righe da un'origine dati come illustrato nell'esempio riportato di seguito.

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

Il metodo Read dell'oggetto DataReader viene utilizzato per ottenere una riga dai risultati della query. È possibile accedere a ogni colonna della riga restituita passando il nome o il riferimento ordinale della colonna al DataReader. Per migliorare le prestazioni, tuttavia, DataReader fornisce una serie di metodi che consentono di accedere ai valori delle colonne nei tipi di dati nativi, GetDateTime, GetDouble, GetGuid, GetInt32 e così via. Per un elenco dei metodi delle funzioni di accesso tipizzate, vedere Classe OleDbDataReader e Classe SqlDataReader. L'utilizzo di metodi di funzioni di accesso tipizzate, quando si conosce il tipo di dati sottostante, riduce il numero di conversioni dei tipi necessarie al momento del recupero del valore della colonna.

Nota   Nella versione Windows Server 2003 di .NET Framework è inclusa una proprietà aggiuntiva per DataReader, HasRows, con cui è possibile determinare se DataReader ha restituito risultati prima di leggerli.

Nell'esempio di codice seguente viene scorso un oggetto DataReader e vengono restituite due colonne da ogni riga.

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 fornisce un flusso di dati non memorizzati nel buffer che consente alla logica di routine di elaborare sequenzialmente i risultati provenienti da un'origine dati. L'utilizzo di DataReader è consigliabile quando si recuperano grandi quantità di dati in quanto i dati non sono memorizzati nella cache.

Chiusura dell'oggetto DataReader

È necessario chiamare sempre il metodo Close una volta terminato l'utilizzo dell'oggetto DataReader.

I parametri di output o i valori restituiti eventualmente presenti nell'oggetto Command, non saranno disponibili fino a quando l'oggetto DataReader non viene chiuso.

Si noti che mentre l'oggetto DataReader è aperto, l'oggetto Connection viene utilizzato esclusivamente da quell'oggetto DataReader. Non è possibile eseguire comandi per l'oggetto Connection, inclusa la creazione di un altro oggetto DataReader, fino a quando il DataReader originale non viene chiuso.

Nota   Non utilizzare i metodi Close o Dispose su Connection, DataReader o altri oggetti gestiti nel metodo Finalize della propria classe. Nei finalizzatori rilasciare solo le risorse non gestite che la classe controlla direttamente. Se la classe non controlla alcuna risorsa non gestita, non includere un metodo Finalize nella definizione della classe. Per ulteriori informazioni, vedere Programmazione coerente con la procedura di garbage collection.

Gruppi di risultati multipli

Se vengono restituiti più gruppi di risultati, l'oggetto DataReader fornisce il metodo NextResult per scorrere in modo sequenziale il gruppo di risultati, come illustrato nell'esempio di codice seguente.

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

Recupero di informazioni sullo schema dall'oggetto DataReader

Mentre un oggetto DataReader è aperto, è possibile recuperare le informazioni sullo schema relative al gruppo di risultati corrente utilizzando il metodo GetSchemaTable. Il metodo restituisce un oggetto DataTable compilato con righe e colonne contenenti le informazioni sullo schema del gruppo di risultati corrente. L'oggetto DataTable contiene una riga per ogni colonna del gruppo di risultati. Ogni colonna della riga nella tabella dello schema viene mappata su una proprietà della colonna restituita nel gruppo di risultati, in cui ColumnName è il nome della proprietà e il valore della colonna è il valore della proprietà. Nell'esempio di codice seguente vengono rilasciate le informazioni sullo schema per l'oggetto 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();
}

Capitoli OLE DB

I rowset gerarchici, o capitoli (tipo OLE DB DBTYPE_HCHAPTER, tipo ADO adChapter), possono essere recuperati utilizzando OleDbDataReader. Quando una query che include un capitolo viene restituita come un DataReader, il capitolo viene restituito come colonna del DataReader e viene esposto come un oggetto DataReader.

È possibile inoltre utilizzare il DataSet di ADO.NET per rappresentare rowset gerarchici utilizzando relazioni padre-figlio tra le tabelle. Per ulteriori informazioni, vedere Creazione e utilizzo di DataSet.

Nell'esempio di codice seguente viene utilizzato il provider MSDataShape per generare una colonna del capitolo contenente gli ordini per ogni cliente presente in un elenco di clienti.

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

REF CURSOR Oracle

Con il provider di dati .NET Framework per Oracle è possibile utilizzare i REF CURSOR Oracle per la restituzione del risultato di una query. I REF CURSOR Oracle vengono restituiti come OracleDataReader.

È possibile recuperare un oggetto OracleDataReader, che rappresenta un REF CURSOR Oracle utilizzando il metodo OracleCommand.ExecuteReader ed è possibile specificare un OracleCommand che restituisca uno o più REF CURSOR Oracle come SelectCommand per un OracleDataAdapter utilizzato per riempire un DataSet.

Per accedere a un REF CURSOR restituito da un'origine dati Oracle, creare un OracleCommand per la propria query e aggiungere un parametro di output che fa riferimento al REF CURSOR all'insieme Parameters di OracleCommand. È necessario che il nome del parametro corrisponda al nome del parametro REF CURSOR della query. Impostare il tipo di parametro su OracleType.Cursor. Il metodo ExecuteReader di OracleCommand restituirà un OracleDataReader per il REF CURSOR.

Se OracleCommand restituisce più REF CURSORS, aggiungere più parametri di output. Per accedere ai diversi REF CURSOR, utilizzare il metodo OracleCommand.ExecuteReader. L'utilizzo di ExecuteReader restituirà un OracleDataReader che fa riferimento al primo REF CURSOR. È quindi possibile utilizzare il metodo OracleDataReader.NextResult per accedere ai REF CURSOR successivi. Benché la corrispondenza tra i parametri dell'insieme OracleCommand.Parameters e i parametri di output di REF CURSOR avvenga per nome, OracleDataReader accederà ai parametri nell'ordine in cui questi sono stati aggiunti all'insieme Parameters.

Si considerino ad esempio il package e il corpo package Oracle riportati di seguito.

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; 

Con il codice che segue viene creato un OracleCommand che restituisce i REF CURSOR dal package Oracle precedente tramite l'aggiunta di due parametri di tipo OracleType.Cursor all'insieme 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;

Con il codice che segue viene restituito il risultato del comando precedente tramite i metodi Read e NextResult di OracleDataReader. I parametri REF CURSOR vengono restituiti in ordine.

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

Nell'esempio che segue viene utilizzato il comando precedente per compilare un DataSet con i risultati del package Oracle.

**Nota   **Per evitare il verificarsi di OverflowException, è necessario gestire le conversioni dei valori di tipo NUMBER Oracle in un tipo .NET Framework valido prima di memorizzare i valori in un DataRow. Per determinare se si è verificata un OverflowException, è possibile utilizzare l'evento FillError. Per ulteriori informazioni sull'evento FillError, vedere Utilizzo di eventi 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);

Vedere anche

Utilizzo di provider di dati .NET Framework per accedere ai dati | Classe DataTable | Classe OleDbDataReader | Classe SqlDataReader