Hämta data med hjälp av en DataReader

Den SqlDataReader läser en framåtriktad sekvens av resultat från en SQL-databas. Den laddar inte hela resultatet via en DataSet, så den är lämplig för att bearbeta rader när de anländer.

I exemplen i den här artikeln förutsätts att en öppen SqlConnection med namnet connection riktar sig mot AdventureWorks-exempeldatabasen.

C#-exemplen kräver using Microsoft.Data.SqlClient;. Exempel som använder SqlDbType eller CommandBehavior kräver också using System.Data;.

Läs rader

Det grundläggande läsarflödet är:

  1. Utför ett kommando med ExecuteReader eller ExecuteReaderAsync.
  2. Fastställ kolumnordinaler för det aktuella resultatet.
  3. Ring Read en gång för varje rad.
  4. Kontrollera nullbara kolumner med IsDBNull innan du anropar en typad getter.
  5. Frigör läsaren innan du återanvänder anslutningen eller läser utdataparametrarna.

Detta exempel cachar kolumnordinaler, använder typade getters och kontrollerar den nullbara Color kolumnen:

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

Att kalla GetString på ett NULL-värde kastar SqlNullValueException, så kolla IsDBNull först.

HasRows rapporterar om det aktuella resultatet innehåller rader, men du kan vanligtvis anropa Read direkt. För ett tomt resultat returnerar falsedet första Read anropet :

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

För stora värden, kör med CommandBehavior.SequentialAccess och använd GetStream, GetTextReader eller GetXmlReader. För mer information, se SqlClient streaming-stöd.

Stäng DataReader

Frigör läsaren så snart bearbetningen är klar. Ett using- eller await using-uttryck stänger den även när radbearbetningen utlöser ett undantag.

Utgångsparametrar, indata-/utdataparametrar och returnvärden för lagrade procedurer är inte slutgiltiga förrän läsaren stänger läsaren:

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

Utan Multiple Active Result Sets (MARS) håller en öppen dataläsare sin anslutning upptagen och ett annat kommando kan inte köras på den anslutningen. Med MARS aktiverat kan separata kommandon ha väntande resultat på en anslutning, men deras operationer är fortfarande inte trådsäkra. För mer information, se Flera aktiva resultatuppsättningar.

Om kommandot angavs med CommandBehavior.CloseConnection, stängs även anslutningen när läsaren stängs. Annars är det koden som öppnade anslutningen som ansvarar för att göra sig av med den.

Läs antal påverkade rader

RecordsAffected rapporterar de rader som har ändrats av INSERT-, UPDATE- och DELETE-instruktioner i kommandot. Läs den efter att läsaren har stängts. Detta exempel återställer uppdateringen:

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

För ett kommando endast för SELECT är RecordsAffected-1. SET NOCOUNT ON döljer antalen, vilket också lämnar värdet vid -1.

Hämta flera resultatmängder genom att använda NextResult

Ett kommando eller en lagrad procedur kan returnera flera resultat. Bearbeta det aktuella resultatet helt och hållet, och anropa sedan NextResult eller 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());

Kolumnantal, namn, ordinalpositioner och typer kan ändras för varje resultat. Beräkna om de cachade ordinalerna efter att NextResult har returnerat true.

Endast en asynkron operation kan vara väntande för en läsare åt gången. Invänta ReadAsync, NextResultAsync eller att en asynkron fältoperation slutförs innan du startar en ny operation på samma läsare.

Hämta schemainformation från DataReader

Anropa GetColumnSchema för en starkt typad beskrivning av kolumnerna i det aktuella resultatet. Anropa GetSchemaTable när befintlig kod behöver schemat i en 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 begär kolumnmetadata utan rader. Schemainformationen beskriver den aktuella resultatmängden. Begär det igen efter att du gått vidare till nästa resultat. Beroende på fråge- och serverbehörigheter kan viss metadata om bastabeller vara otillgänglig.