Hibák és üzenetek kezelése

A Microsoft.Data.SqlClient a(z) SqlException segítségével jelenti a szerver- és szolgáltatóhibákat. Egy adatbázis-művelet több hibát is visszaadhat, ezért vizsgáld meg a kivétel Errors gyűjteményét ahelyett, hogy csak az egyesített Message elemre hagyatkoznál.

Az adatbázismotorból érkező figyelmeztetések és információs üzenetek a InfoMessage útján érkeznek. Iratkozz fel, mielőtt megnyitod a csatlakozást vagy végrehajtod azt a parancsot, amely előidézheti az üzenetet.

Kezelje az adatbázis hibáját a következő sorrendben:

  1. Kapd el a(z) SqlException elemet egy hibahatáron belül, amely műveleti környezetet adhat hozzá, vagy válassz egy helyreállítási műveletet.
  2. Vizsgálja meg az összes SqlError elemet a(z) Errors gyűjteményben.
  3. Rögzítsen strukturált diagnosztikai mezőket anélkül, hogy rögzítené a hitelesítő adatokat vagy érzékeny értékeket.
  4. Ellenőrizd a kapcsolati állapotot egy kapcsolódással kapcsolatos hiba után.
  5. Csak akkor próbálja újra, ha a hiba átmeneti, és a művelet biztonságosan megismételhető.

A cikkben szereplő példák feltételezik, hogy egy nyitott SqlConnection, amelynek neve connection, a(z) AdventureWorks mintaadatbázist célozza meg. A C#-példák megkövetelik using Microsoft.Data.SqlClient;.

Információs üzenetek fogadása

Az InfoMessage esemény alacsony súlyosságú szerverüzeneteket kap, beleértve a PRINT-ből származó kimenetet is. Egy esemény több SqlError objektumot is tartalmazhat. Iratkozz fel, mielőtt végrehajtanád azt a parancsot, amely üzenetet generálhat:

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

A részlet így szól:

Info 0, class 0: Connected and ready.

Catch SqlException

Tegyél SqlException az adatbázis-művelet köré, amelyet kezelni vagy további információval kiegészíteni tudsz. Ne használj általános catch blokkot arra, hogy egy sikertelen adatbázis-műveletet sikeres eredménnyé alakíts.

Egy adatbázis-művelet több hibát is visszaadhat. A(z) Errors minden SqlError eleme strukturált mezőket tartalmaz:

Property Meaning
Number Adatbázis motor vagy szolgáltató hibaszáma.
Class Súlyossága.
State Szerver által definiált állapot, amely meg tudja különböztetni az ugyanazon hibaszámhoz tartozó helyeket.
LineNumber Jelentett sorszám.
Procedure Eljárás vagy trigger neve, ha meg van adva.
Server A hibához megadott szervernév.
Message Ember által olvasható hibaszöveg.

A kényelmi tulajdonságok a SqlException elemnél, beleértve a Number, Class, State, LineNumber, Procedure és Server elemeket, csak az első hibát tükrözik. A következő részlet egy üzenetet nyomtat, majd ugyanabban a kötegben hibát generál. Olvas exception.Number , majd végigiterál Errors minden bejegyzés jelentéséhez:

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

A részlet így szól:

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.

A PRINT kimenet nem váltja ki a InfoMessage-et. Ha egy tétel olyan hibát ad vissza, amely miatt SqlException történik, a szolgáltató hozzáfűzi az adott tétel információs üzeneteit a(z) SqlException.Errors végéhez.

Ez a részlet csak a kontrollált hibát elfogja, hogy megmutassa a mezőit. Az alkalmazáskódban throw; segítségével dobd tovább a kivételt, hacsak a catch blokk nem végez tényleges helyreállítást. Döntéseket dokumentált hibaszámok és működési kontextus alapján hozz meg. Ne parzáld a lokalizált üzenetszöveget.

A kapcsolat állapotának ellenőrzése

Az adatbázis motor minden üzenethez egy súlyosságosztályt rendel hozzá. Az alsóbb osztályok csak tájékoztató jellegűek. A hibaként kezelt osztályok SqlException eredményeznek. A nagy súlyosságú hibák miatt a kapcsolat bezáródhat vagy megszakadhat.

Hiba után ellenőrizd State-t, mielőtt újra használod a kapcsolatot:

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

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

Ne feltételezd, hogy egy kapcsolódás használható marad, mert a kivételt kezelték. Egy 16-os súlyosságú hiba általában nyitva hagyja, de a kapcsolatmegszakító hibák bezárhatják azt. A lezárt vagy megszakadt kapcsolatot el kell dobni, és egy másik kapcsolatot kell lekérni a kapcsolatkészletből. Ne alapozd az újrapróbálkozási szabályzatot pusztán a súlyosságra. A műveletnek biztonságosan megismételhetőnek kell lennie.

Hibás batch parancs azonosítása

Ha a SqlBatch hibás, SqlException.BatchCommand hivatkozhat a hozzá tartozó SqlBatchCommand. A tulajdonság null, ha a szolgáltató nem tudja a hibát egyetlen batch parancsnak tulajdonítani. A következő részlet két kötegelt parancsot tartalmaz. A második megbukik:

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

A részlet így szól:

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

A parancsszöveg érzékeny literálisokat tartalmazhat rosszul paraméterezett alkalmazásokban. Részesítse előnyben a stabil alkalmazásművelet-neveket az éles naplókban, és alapértelmezés szerint ne naplózza a paraméterértékeket.

Felhasználói hibák irányítása az InfoMessage-re

FireInfoMessageEventOnUserErrors alapértelmezés szerint false. Amikor beállítod true, a 16-os vagy annál alacsonyabb súlyosságú hibák a InfoMessage kezelőhöz kerülnek SqlException kiváltása helyett:

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

Nincs kivétel. A részlet így szól:

Info 50000, class 16: Routed to InfoMessage.

Ezt az opciót csak akkor használjuk, ha az alkalmazás szándékosan eseményalapú hibamodellt használ, mert a hívó kód már nem lát kivételt. A 17-es vagy annál magasabb súlyosságú hibák továbbra is riasztást váltanak ki SqlException. Egyszer állítsd be a tulajdonságot a kapcsolat konfigurációjának részeként, ahelyett, hogy az egyes parancsok előtt és után ki-be kapcsolnád.

Napló diagnosztikai kontextusa

Elég strukturált kontextust rögzíts, hogy összefüggésbe hozzuk a hibát adatok felfedése nélkül. A következő részlet naplózza az alkalmazásművelet nevét, a ClientConnectionId elemet és minden egyes SqlError mezőit. ClientConnectionId segít összekapcsolni egy kliens hibáját a szerver- és hálózati diagnosztikával. Ez nem egy képesítés.

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

Az érték ClientConnectionId minden kapcsolatnál eltérő. A részlet hasonló kimenetet ad ki:

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

Rögzítsd azt is, hogy aktív volt-e egy tranzakció, és ha BatchCommand elérhető, melyik csomagparancs hiúsult meg. Csak akkor vegyük fel a tisztított szerver- és adatbázisazonosítókat, ha a szervezeti szabályzat engedi. Ne naplózz kapcsolati karakterláncokat, hozzáférési tokeneket, jelszavakat vagy érzékeny paraméterértékeket. Használj diagnosztikai számlálókat, eseményforrás követést és a SqlClient hibakeresési útmutatót a mélyebb vizsgálathoz.