Haal gegevens op met een DataReader

Deze SqlDataReader leest een forward-only reeks resultaten uit een SQL-database. Het laadt het volledige resultaat niet in een DataSet, dus het is geschikt om rijen te verwerken zodra ze binnenkomen.

De voorbeelden in dit artikel gaan ervan uit dat een open SqlConnection met de naam connection is gericht op de AdventureWorks-voorbeelddatabase.

De C#-voorbeelden vereisen using Microsoft.Data.SqlClient;. Voorbeelden die gebruikmaken van SqlDbType of CommandBehavior, vereisen ook using System.Data;.

Lees rijen

De basisworkflow voor lezers is:

  1. Voer een commando uit met ExecuteReader of ExecuteReaderAsync.
  2. Bepaal kolomordinalen voor het huidige resultaat.
  3. Roep één keer Read per rij.
  4. Controleer nulleerbare kolommen met IsDBNull voordat je een getypeerde getter aanroept.
  5. Verwijder de reader voordat je de verbinding hergebruikt of de uitvoerparameters leest.

Dit voorbeeld slaat kolomindexen op in de cache, gebruikt getypeerde getters en controleert de Color-kolom die null-waarden kan bevatten:

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

Het aanroepen van GetString op een NULL-waarde veroorzaakt SqlNullValueException, dus controleer eerst IsDBNull.

HasRows rapporteert of het huidige resultaat rijen bevat, maar je kunt Read meestal direct aanroepen. Voor een leeg resultaat geeft de eerste Read aanroep :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()}");

Voor grote waarden voer je uit met CommandBehavior.SequentialAccess en gebruik GetStream, GetTextReader, of GetXmlReader. Voor meer informatie, zie SqlClient streaming support.

De DataReader sluiten

Geef de reader vrij zodra de verwerking is afgerond. Een await using- of using-instructie sluit het zelfs als de rijverwerking een uitzondering genereert.

Outputparameters, input/outputparameters en return values van stored procedures zijn pas definitief totdat de lezer wordt gesloten:

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

Zonder Multiple Active Result Sets (MARS) houdt een open reader zijn verbinding bezig en kan een ander commando niet op die verbinding worden uitgevoerd. Met MARS ingeschakeld kunnen aparte commando's op één verbinding in wachting zijn, maar hun bewerkingen zijn nog steeds niet thread-veilig. Voor meer informatie, zie Multiple Active Result Sets.

Als het commando is doorgegeven CommandBehavior.CloseConnection, dan wordt bij het sluiten van de reader ook de verbinding gesloten. Anders blijft de code die de verbinding opende verantwoordelijk voor het vrijgeven ervan.

Lees het aantal getroffen rijen

RecordsAffected rapporteert de door INSERT, UPDATE en DELETE-statements gewijzigde rijen in het commando. Lees het nadat de lezer het heeft gesloten. Dit voorbeeld draait zijn update terug:

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

Voor een SELECT-only commando RecordsAffected is -1. SET NOCOUNT ON onderdrukt de tellingen, waardoor de waarde ook op -1 blijft.

Haal meerdere resultaatsets op met NextResult

Eén commando of opgeslagen procedure kan meerdere resultaten opleveren. Verwerk het huidige resultaat volledig en roep daarna NextResult of NextResultAsync aan:

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

Het aantal kolommen, namen, rangnummers en typen kan voor elk resultaat veranderen. Bereken gecachte ordinaalgetallen opnieuw nadat NextResulttrue retourneert.

Er kan slechts één asynchrone bewerking tegelijk voor een lezer in behandeling zijn. Wacht op ReadAsync, NextResultAsync, of een asynchrone veldoperatie voordat je een nieuwe bewerking op dezelfde lezer start.

Schemagegevens ophalen uit DataReader

Roep GetColumnSchema aan voor een sterk getypte beschrijving van de kolommen van het huidige resultaat. Roep GetSchemaTable aan wanneer bestaande code het schema in een 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 vraagt kolommetadata zonder rijen op. Schema-informatie beschrijft de huidige resultaatset. Vraag het opnieuw nadat je naar het volgende resultaat bent gegaan. Afhankelijk van de query- en serverrechten kan sommige basistabelmetadata niet beschikbaar zijn.