Afhandelen van fouten en berichten

Microsoft. Data.SqlClient rapporteert server- en providerstoringen via SqlException. Een databasebewerking kan meerdere fouten teruggeven, dus inspecteer de Errors verzameling van de uitzondering in plaats van alleen te vertrouwen op de gecombineerde Message.

Waarschuwingen en informatieve berichten van de database-engine worden doorgegeven via InfoMessage. Abonneer je voordat je de verbinding opent of het commando uitvoert dat het bericht kan genereren.

Behandel een databasefout in deze volgorde:

  1. Vang SqlException bij een grens die operationele context kan toevoegen of kies een herstelactie.
  2. Controleer elke SqlError in de Errors collectie.
  3. Registreer gestructureerde diagnostische velden zonder inloggegevens of gevoelige waarden vast te leggen.
  4. Controleer de verbindingsstatus na een verbindingsgerelateerde storing.
  5. Probeer het alleen opnieuw wanneer de fout tijdelijk is en de bewerking veilig is om te herhalen.

In de voorbeelden in dit artikel wordt ervan uitgegaan dat een open SqlConnection met de naam connection gericht is op de AdventureWorks-voorbeelddatabase. De C#-voorbeelden vereisen using Microsoft.Data.SqlClient;.

Ontvang informatieve berichten

Het InfoMessage event ontvangt serverberichten met lage ernst, inclusief output van PRINT. Eén gebeurtenis kan meerdere SqlError objecten bevatten. Abonneer je voordat je het commando uitvoert dat een bericht kan genereren:

connection.InfoMessage += (_, eventArgs) =>
{
    foreach (SqlError message in eventArgs.Errors)
    {
        Console.WriteLine(
            $"Info {message.Number}, class {message.Class}: {message.Message}");
    }
};

using var command = new SqlCommand(
    "PRINT N'Connected and ready.';",
    connection);
command.ExecuteNonQuery();

Het fragment toont:

Info 0, class 0: Connected and ready.

Catch SqlException

Vang SqlException om de database-operatie heen die je kunt beheren of verrijken. Gebruik geen algemene catch om een mislukte databaseoperatie om te zetten in een succesvol resultaat.

Een databasebewerking kan meer dan één fout teruggeven. Elke SqlError in Errors omvat gestructureerde velden:

Property Meaning
Number Foutnummer van database-engine of provider.
Class Ernstniveau.
State Server-gedefinieerde toestand die locaties kan onderscheiden voor hetzelfde foutnummer.
LineNumber Gemeld regelnummer
Procedure Naam van procedure of trigger wanneer aangeleverd.
Server Servernaam gerapporteerd bij de fout.
Message Leesbare fouttekst.

De gemakseigenschappen op SqlException, waaronder Number, Class, , State, LineNumber, Procedure, en Server, weerspiegelen alleen de eerste fout. Het volgende fragment drukt een bericht af en genereert vervolgens een fout in dezelfde batch. Het leest exception.Number en itereert vervolgens over Errors om elke invoer te rapporteren:

using (var failing = new SqlCommand("""
    PRINT N'Starting controlled operation.';
    RAISERROR (N'The controlled operation failed.', 16, 1);
    """, connection))
{
    try
    {
        await failing.ExecuteNonQueryAsync();
    }
    catch (SqlException exception)
    {
        Console.WriteLine($"First error number: {exception.Number}");

        foreach (SqlError error in exception.Errors)
        {
            Console.WriteLine(
                $"Number={error.Number}; Class={error.Class}; " +
                $"State={error.State}; Message={error.Message}");
        }
    }
}

Het fragment luidt:

First error number: 50000
Number=50000; Class=16; State=1; Message=The controlled operation failed.
Number=0; Class=0; State=1; Message=Starting controlled operation.

De PRINT output verhoogt InfoMessageniet. Wanneer een batch een foutmelding geeft die SqlException veroorzaakt, voegt de provider de informatieve berichten van die batch toe aan het einde van SqlException.Errors.

Dit fragment vangt de gecontroleerde fout alleen op om de velden ervan te tonen. In applicatiecode: werp opnieuw op met throw;, tenzij het catchblok voor daadwerkelijk herstel kiest. Neem beslissingen op basis van gedocumenteerde foutnummers en de operationele context. Parse geen gelokaliseerde berichttekst.

De verbindingsstatus controleren

De database-engine wijst aan elk bericht een ernstklasse toe. Subklassen zijn informatief. Klassen die als fouten worden behandeld, produceren SqlException. Fouten met hoge ernst kunnen de verbinding sluiten of verbreken.

Controleer na een foutmelding State voordat je de verbinding opnieuw gebruikt:

using var command = new SqlCommand(
    "RAISERROR (N'Controlled failure.', 16, 1);",
    connection);

try
{
    command.ExecuteNonQuery();
}
catch (SqlException)
{
    Console.WriteLine($"Connection state: {connection.State}");
}

Ga er niet van uit dat een verbinding bruikbaar blijft omdat de uitzondering is opgevangen. Een fout van ernst 16 laat het meestal open, maar fouten die de verbinding verbreken kunnen het sluiten. Geef een gesloten of verbroken verbinding vrij en haal een andere verbinding uit de pool. Codeer het beleid voor nieuwe pogingen niet alleen op basis van de ernst. De operatie moet ook veilig herhaald kunnen worden.

Identificeer een mislukte batchopdracht

Wanneer een SqlBatch faalt, kan SqlException.BatchCommand naar de bijbehorende SqlBatchCommand verwijzen. De eigenschap is null wanneer de provider de mislukking niet aan één batchcommando kan toeschrijven. Het volgende fragment bevat twee batchcommando's. De tweede mislukt:

using (var batch = new SqlBatch(connection))
{
    batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1;"));
    batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1 / 0;"));

    try
    {
        await batch.ExecuteNonQueryAsync();
    }
    catch (SqlException exception)
    {
        Console.WriteLine($"Error {exception.Number}: {exception.Message}");

        if (exception.BatchCommand is SqlBatchCommand failedCommand)
        {
            Console.WriteLine($"Failed command: {failedCommand.CommandText}");
        }
    }
}

Het fragment geeft weer:

Error 8134: Divide by zero error encountered.
Failed command: SELECT 1 / 0;

Commandotekst kan gevoelige literals bevatten in slecht geparametriseerde applicaties. Geef de voorkeur aan stabiele applicatiebewerkingsnamen in productielogs en log standaard geen parameterwaarden.

Stuur gebruikersfouten naar InfoMessage

FireInfoMessageEventOnUserErrors wordt standaard ingesteld op false. Wanneer je het instelt op true, gaan fouten met ernst 16 of lager naar de InfoMessage-handler in plaats van SqlException te genereren:

connection.InfoMessage += (_, eventArgs) =>
{
    foreach (SqlError message in eventArgs.Errors)
    {
        Console.WriteLine(
            $"Info {message.Number}, class {message.Class}: {message.Message}");
    }
};
connection.FireInfoMessageEventOnUserErrors = true;

using var command = new SqlCommand(
    "RAISERROR (N'Routed to InfoMessage.', 16, 1);",
    connection);
command.ExecuteNonQuery();

Er wordt geen uitzondering opgeworpen. Het fragment geeft het volgende weer:

Info 50000, class 16: Routed to InfoMessage.

Gebruik deze optie alleen wanneer de applicatie een bewust event-based foutmodel heeft, omdat de aanroepende code geen uitzondering meer ziet. Fouten met ernst 17 of hoger geven nog steeds SqlException. Stel de eigenschap één keer in als onderdeel van de verbindingsconfiguratie in plaats van deze voor en na afzonderlijke opdrachten in en uit te schakelen.

Diagnostische logcontext

Leg voldoende gestructureerde context vast om de mislukking te correleren zonder data bloot te stellen. Het volgende fragment registreert een applicatie-operatienaam, de ClientConnectionId, en de velden van elke SqlError. ClientConnectionId Helpt een clientstoring te correleren met server- en netwerkdiagnostiek. Het is geen inloggegeven.

const string operation = "LoadOrders";

using (var load = new SqlCommand("SELECT 1 / 0;", connection))
{
    try
    {
        await load.ExecuteScalarAsync();
    }
    catch (SqlException exception)
    {
        Console.WriteLine(
            $"Operation={operation}; " +
            $"ClientConnectionId={exception.ClientConnectionId}");

        foreach (SqlError error in exception.Errors)
        {
            Console.WriteLine(
                $"Number={error.Number}; Class={error.Class}; " +
                $"State={error.State}; Procedure={error.Procedure}; " +
                $"Line={error.LineNumber}");
        }
    }
}

De ClientConnectionId waarde verschilt per verbinding. Het fragment geeft vergelijkbare uitvoer weer als volgt:

Operation=LoadOrders; ClientConnectionId=<connection ID>
Number=8134; Class=16; State=1; Procedure=; Line=1

Noteer ook of een transactie actief was en, indien BatchCommand beschikbaar, welk batchcommando is mislukt. Voeg alleen gesaniteerde server- en database-identificaties toe als het organisatiebeleid dit toestaat. Log geen verbindingsstrings, toegangstokens, wachtwoorden of gevoelige parameterwaarden. Gebruik diagnostische tellers, event source tracing en de SqlClient probleemoplossingsgids voor dieper onderzoek.