Získejte data pomocí DataReaderu

SqlDataReader čte sekvenci výsledků pouze pro posun vpřed z databáze SQL. Nenačítá kompletní výsledek do DataSet, takže je vhodný pro zpracování řádků, jakmile přicházejí.

Příklady v tomto článku předpokládají, že otevřené připojení SqlConnection s názvem connection cílí na ukázkovou databázi AdventureWorks.

Příklady v C# vyžadují using Microsoft.Data.SqlClient;. Příklady, které používají SqlDbType nebo CommandBehavior také vyžadují using System.Data;.

Přečtěte řádky

Základní pracovní postup čtenáře je:

  1. Proveďte příkaz s ExecuteReader nebo ExecuteReaderAsync.
  2. Určit pořadí sloupců pro aktuální výsledek.
  3. Na každý řádek zavolejte Read jednou.
  4. Před zavoláním typovaného getteru zkontrolujte nullable sloupce pomocí IsDBNull.
  5. Čtečku dat zlikvidujte před opětovným použitím připojení nebo čtením výstupních parametrů.

Tento příklad ukládá pořadí sloupců do cache, používá typované gettery a kontroluje nulovatelný sloupec 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)"}");
}

Volání GetString na hodnotě NULL hodí SqlNullValueException, takže nejdřív zkontrolujte IsDBNull.

HasRows hlásí, zda aktuální výsledek obsahuje řádky, ale obvykle můžete volat Read přímo. Pro prázdný výsledek první Read volání vrací 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()}");

Pro velké hodnoty spusťte pomocí CommandBehavior.SequentialAccess a použijte GetStream, GetTextReader nebo GetXmlReader. Pro více informací viz podpora streamování SqlClient.

Zavření čtečky dat

Čtečku uvolněte hned po dokončení zpracování. Příkaz await using nebo using ji uzavírá i když zpracování řádku vyvolá výjimku.

Výstupní parametry, vstupní/výstupní parametry a návratové hodnoty uložených procedur nejsou konečné, dokud se reader neuzavře:

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

Bez Multiple Active Result Sets (MARS) otevřená čtečka udržuje své připojení zaneprázdněné a jiný příkaz na tomto připojení nemůže být spuštěn. S povoleným MARS mohou mít samostatné příkazy čekající výsledky na jednom připojení, ale jejich operace stále nejsou bezpečné pro vlákna. Pro více informací viz Multiple Active Result Sets.

Pokud je předán příkaz CommandBehavior.CloseConnection, zavření čtečky také ukončí spojení. Jinak zůstává za jeho uvolnění odpovědný kód, který spojení otevřel.

Přečíst počet ovlivněných řádků

RecordsAffected hlásí řádky změněné příkazy INSERT, UPDATE a DELETE v rámci příkazu. Přečtěte si ji poté, co se čtečka zavře. Tento příklad vrátí svou aktualizaci zpět:

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

U příkazu pouze pro SELECT je RecordsAffected-1. SET NOCOUNT ON potlačuje počty, což také ponechává hodnotu na -1.

Získejte více sad výsledků pomocí NextResult

Jeden příkaz nebo uložená procedura může vrátit více výsledků. Zpracujte aktuální výsledek kompletně a poté zavolejte NextResult nebo 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());

Počet sloupců, názvy, ordinály a typy se mohou měnit pro každý výsledek. Po návratu true znovu vypočítejte kešované ordinály NextResult.

U čtečky může být v daném okamžiku čekající pouze jedna asynchronní operace. Před zahájením další operace na stejné čtečce vyčkejte na ReadAsync, NextResultAsync nebo asynchronní operaci pole (field operation).

Získání informací o schématu z DataReaderu

Zavolejte GetColumnSchema pro silně typovaný popis sloupců aktuálního výsledku. Použijte GetSchemaTable, když existující kód potřebuje schéma v 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 požaduje metadata sloupců bez řádků. Informace o schématu popisují aktuální množinu výsledků. Požádejte o to znovu, až přejdete na další výsledek. V závislosti na dotazu a oprávněních serveru mohou být některá metadata základních tabulek nedostupná.