Recupera i dati utilizzando un DataReader

SqlDataReader legge una sequenza di risultati solo in avanti da un database SQL. Non carica il risultato completo in un DataSet, quindi è adatto per l'elaborazione delle righe man mano che arrivano.

Gli esempi in questo articolo presuppongono che una connessione connection aperta denominata SqlConnection sia destinata al database di esempio AdventureWorks.

Gli esempi di C# richiedono using Microsoft.Data.SqlClient;. Esempi che usano SqlDbType o CommandBehavior richiedono using System.Data;anche .

Leggi righe

Il flusso di lavoro base del lettore è:

  1. Esegui un comando con ExecuteReader o ExecuteReaderAsync.
  2. Determina gli indici di colonna per il risultato corrente.
  3. Chiama Read una volta per ogni riga.
  4. Controlla le colonne nullable con IsDBNull prima di chiamare un getter tipizzato.
  5. Elimina il reader prima di riutilizzare la connessione o leggere i parametri di uscita.

Questo esempio memorizza in cache gli ordinali delle colonne, utilizza getter tipizzati e controlla la colonna nullabile Color :

using var command = new SqlCommand("""
    SELECT ProductID, Name, Color
    FROM Production.Product
    WHERE ProductSubcategoryID = @subcategoryId
    ORDER BY ProductID;
    """, connection);
command.Parameters.Add("@subcategoryId", SqlDbType.Int).Value = 2;

using SqlDataReader reader = command.ExecuteReader();
int idOrdinal = reader.GetOrdinal("ProductID");
int nameOrdinal = reader.GetOrdinal("Name");
int colorOrdinal = reader.GetOrdinal("Color");

while (reader.Read())
{
    int id = reader.GetInt32(idOrdinal);
    string name = reader.GetString(nameOrdinal);
    string? color = reader.IsDBNull(colorOrdinal)
        ? null
        : reader.GetString(colorOrdinal);

    Console.WriteLine($"{id} | {name} | {color ?? "(no color)"}");
}

Chiamare GetString su un valore NULL genera SqlNullValueException, quindi controlla prima IsDBNull

HasRows Segnala se il risultato attuale contiene righe, ma di solito puoi chiamare Read direttamente. Per un risultato vuoto, la prima Read chiamata restituisce false:

using var command = new SqlCommand("""
    SELECT ProductID
    FROM Production.Product
    WHERE ProductID = @productId;
    """, connection);
command.Parameters.Add("@productId", SqlDbType.Int).Value = -1;

using SqlDataReader reader = command.ExecuteReader();
Console.WriteLine($"HasRows: {reader.HasRows}");
Console.WriteLine($"First Read: {reader.Read()}");

Per valori grandi, esegui con CommandBehavior.SequentialAccess e usa GetStream, GetTextReader, o GetXmlReader. Per maggiori informazioni, consulta il supporto allo streaming di SqlClient.

Chiudi il DataReader

Rilasciare il reader non appena la lavorazione è terminata. Un'istruzione using o await using istruzione la chiude anche quando l'elaborazione delle righe genera un'eccezione.

I parametri di output, i parametri di input/output e i valori di ritorno delle stored procedure non sono definitivi fino alla chiusura del lettore:

using var command = new SqlCommand("""
    SELECT TOP (5) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID;
    SET @count = @@ROWCOUNT;
    """, connection);

SqlParameter count = command.Parameters.Add("@count", SqlDbType.Int);
count.Direction = ParameterDirection.Output;

using (SqlDataReader reader = command.ExecuteReader())
{
    while (reader.Read())
    {
        Console.WriteLine(reader.GetString(1));
    }
}

Console.WriteLine($"Rows returned: {count.Value}");

Senza Multiple Active Result Set (MARS), un lettore aperto mantiene occupata la sua connessione e un altro comando non può essere eseguito su quella connessione. Con MARS abilitato, comandi separati possono avere risultati in attesa su una connessione, ma le loro operazioni non sono comunque thread-safe. Per maggiori informazioni, vedi Multiple Active Result Sets.

Se viene passato il comando CommandBehavior.CloseConnection, anche la chiusura del reader chiude la connessione. Altrimenti, il codice che ha aperto la connessione rimane responsabile della chiusura.

Leggere i conteggi delle righe interessate

RecordsAffected riporta le righe modificate dalle istruzioni INSERT, UPDATE e DELETE nel comando. Leggilo dopo che Reader si è chiuso. Questo esempio ripristina il proprio aggiornamento:

using SqlTransaction transaction = connection.BeginTransaction();
using var command = new SqlCommand("""
    UPDATE Production.Product
    SET ModifiedDate = ModifiedDate
    WHERE ProductID IN (706, 707);

    SELECT ProductID, Name
    FROM Production.Product
    WHERE ProductID IN (706, 707)
    ORDER BY ProductID;
    """, connection, transaction);

SqlDataReader reader = command.ExecuteReader();
using (reader)
{
    while (reader.Read())
    {
        Console.WriteLine($"{reader.GetInt32(0)} | {reader.GetString(1)}");
    }
}

Console.WriteLine($"RecordsAffected: {reader.RecordsAffected}");
transaction.Rollback();

Per un comando con solo SELECT, RecordsAffected è -1. SET NOCOUNT ON sopprime i conteggi, lasciando anche il valore a -1.

Recupera più set di risultati usando NextResult

Un comando o una procedura memorizzata può restituire più risultati. Elaborare completamente il risultato corrente, e poi chiamare NextResult o NextResultAsync

using var command = new SqlCommand("""
    SELECT TOP (3) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID;

    SELECT COUNT(*) AS ProductCount
    FROM Production.Product;
    """, connection);

using SqlDataReader reader = command.ExecuteReader();
int result = 1;

do
{
    Console.WriteLine($"Result {result++}: {reader.FieldCount} column(s)");
    while (reader.Read())
    {
        var values = new object[reader.FieldCount];
        reader.GetValues(values);
        Console.WriteLine(string.Join(" | ", values));
    }
}
while (reader.NextResult());

Il numero di colonne, nomi, ordinali e tipi possono cambiare per ogni risultato. Ricalcola gli ordinali nella cache dopo che NextResult restituisce true.

Può essere in attesa di un lettore solo un'operazione asincrona alla volta. Attendere ReadAsync, NextResultAsync, o un'operazione asincrona su un campo prima di iniziare un'altra operazione sullo stesso lettore.

Ottenere informazioni sullo schema da DataReader

Chiama GetColumnSchema per una descrizione fortemente tipizzata delle colonne del risultato corrente. Invoca GetSchemaTable quando il codice esistente ha bisogno dello schema in un DataTable:

using var command = new SqlCommand("""
    SELECT ProductID, Name, Color
    FROM Production.Product;
    """, connection);

using SqlDataReader reader = command.ExecuteReader(
    CommandBehavior.SchemaOnly);

foreach (System.Data.Common.DbColumn column in reader.GetColumnSchema())
{
    Console.WriteLine(
        $"{column.ColumnOrdinal}: {column.ColumnName} " +
        $"({column.DataTypeName}), nullable={column.AllowDBNull}");
}

CommandBehavior.SchemaOnly richiede metadati delle colonne senza restituire righe. Le informazioni dello schema descrivono il set di risultati corrente. Richiedelo di nuovo dopo essere passato al risultato successivo. A seconda della query e dei permessi del server, alcuni metadati della tabella base potrebbero non essere disponibili.