Tangani kesalahan dan pesan

Microsoft. Data.SqlClient melaporkan kegagalan server dan penyedia melalui SqlException. Operasi basis data dapat mengembalikan beberapa kesalahan, jadi periksa koleksi milik pengecualian Errors daripada hanya mengandalkan nilai gabungan Message.

Peringatan dan pesan informasi dari mesin basis data dikirim melalui InfoMessage. Lakukan subscribe sebelum membuka koneksi atau menjalankan perintah yang dapat menghasilkan pesan.

Tangani kegagalan basis data dalam urutan berikut:

  1. Tangkap SqlException pada boundary yang dapat menambahkan konteks operasi atau memilih aksi pemulihan.
  2. Periksa setiap SqlError dalam koleksi Errors.
  3. Rekam bidang diagnostik terstruktur tanpa mencatat kredensial atau nilai sensitif.
  4. Periksa status koneksi setelah kegagalan terkait koneksi.
  5. Coba ulang hanya ketika kesalahan bersifat sementara dan operasi aman untuk diulang.

Contoh dalam artikel ini mengasumsikan bahwa sebuah koneksi terbuka SqlConnection bernama connection menargetkan database contoh AdventureWorks. Contoh C# memerlukan using Microsoft.Data.SqlClient;.

Terima pesan informatif

event InfoMessage menerima pesan server dengan tingkat keparahan rendah, termasuk output dari PRINT. Satu event dapat berisi beberapa SqlError objek. Berlangganan sebelum menjalankan perintah yang dapat menghasilkan pesan:

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

Cuplikan tersebut mencetak:

Info 0, class 0: Connected and ready.

Tangkap SqlException

Tangkap SqlException di sekitar operasi database yang bisa Anda tangani atau tambahkan konteks. Jangan gunakan broad catch untuk mengubah operasi database yang gagal menjadi hasil yang sukses.

Operasi basis data dapat mengembalikan lebih dari satu kesalahan. Setiap SqlError dalam Errors mencakup bidang terstruktur:

Property Meaning
Number Nomor kesalahan mesin database atau provider.
Class Keparahan.
State Status yang didefinisikan server yang dapat membedakan lokasi untuk nomor kesalahan yang sama.
LineNumber Nomor baris yang dilaporkan.
Procedure Nama prosedur atau pemicu jika disediakan.
Server Nama server yang dilaporkan untuk kesalahan tersebut.
Message Pesan kesalahan yang mudah dibaca pengguna.

Properti kemudahan pada SqlException, termasuk Number, Class, State, LineNumber, Procedure, dan Server, hanya mencerminkan kesalahan pertama. Kutipan berikut mencetak pesan lalu memunculkan error dalam batch yang sama. Aplikasi ini membaca exception.Number dan kemudian melakukan iterasi Errors untuk melaporkan setiap entri:

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

Cuplikan tersebut mencetak:

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.

Outputnya PRINT tidak memunculkan InfoMessage. Ketika sebuah batch mengembalikan kesalahan yang memunculkan SqlException, penyedia menambahkan pesan informasi batch tersebut ke akhir SqlException.Errors.

Kutipan ini menangani kesalahan terkontrol hanya untuk menampilkan bidangnya. Dalam kode aplikasi, lempar ulang dengan throw; kecuali blok tangkap memilih aksi pemulihan yang nyata. Ambil keputusan berdasarkan nomor kesalahan yang terdokumentasi dan konteks operasi. Jangan mengurai teks pesan yang dilokalisasi.

Periksa status koneksi

Mesin basis data menetapkan kelas tingkat keparahan untuk setiap pesan. Kelas turunan bersifat informatif. Kelas yang diperlakukan sebagai kesalahan menghasilkan SqlException. Kesalahan tingkat keparahan tinggi dapat membuat koneksi tertutup atau rusak.

Setelah terjadi kesalahan, periksa State sebelum menggunakan kembali koneksi:

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

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

Jangan berasumsi koneksi tetap bisa digunakan hanya karena pengecualian tertangkap. Kesalahan tingkat keparahan 16 biasanya membuatnya terbuka, tetapi kegagalan yang memutus koneksi dapat menutupnya. Lepaskan koneksi yang sudah ditutup atau rusak dan dapatkan koneksi lain dari pool koneksi. Jangan encode kebijakan coba ulang hanya dari tingkat keparahan saja. Operasi juga harus aman untuk diulang.

Identifikasi perintah batch yang gagal

Ketika SqlBatch gagal, SqlException.BatchCommand dapat merujuk pada SqlBatchCommand yang terkait. Properti ini adalah null jika penyedia tidak dapat mengaitkan kegagalan ke satu perintah batch tertentu. Kutipan berikut menjalankan dua perintah batch. Yang kedua gagal:

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

Cuplikan tersebut menampilkan:

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

Teks perintah dapat mengandung literal sensitif dalam aplikasi yang diparameterisasi dengan buruk. Utamakan nama operasi aplikasi yang stabil dalam log produksi, dan jangan mencatat nilai parameter secara default.

Arahkan kesalahan pengguna ke InfoMessage

FireInfoMessageEventOnUserErrors defaultnya adalah false. Ketika Anda mengaturnya ke true, kesalahan dengan tingkat keparahan 16 atau lebih rendah akan masuk ke handler InfoMessage, bukan memunculkan SqlException:

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

Tidak ada pengecualian yang dilemparkan. Cuplikan tersebut mencetak:

Info 50000, class 16: Routed to InfoMessage.

Gunakan opsi ini hanya ketika aplikasi memiliki model kesalahan berbasis peristiwa yang disengaja, karena kode pemanggil tidak lagi melihat pengecualian. Kesalahan dengan tingkat keparahan 17 atau lebih tinggi tetap memunculkan SqlException. Atur properti sekali sebagai bagian dari konfigurasi koneksi daripada mengaktifkan/menonaktifkannya di sekitar perintah individual.

Konteks diagnostik log

Tangkap konteks terstruktur yang cukup untuk mengkorelasikan kegagalan tanpa mengekspos data. Kutipan berikut mencatat nama operasi aplikasi, ClientConnectionId, dan field dari setiap SqlError. ClientConnectionId membantu menghubungkan kegagalan klien dengan diagnostik server dan jaringan. Itu bukan kredensial.

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

Nilai ClientConnectionId berbeda pada setiap koneksi. Cuplikan ini mencetak output yang mirip dengan:

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

Catat juga apakah transaksi aktif dan, jika BatchCommand tersedia, perintah batch mana yang gagal. Sertakan pengenal server dan basis data yang sudah disterilkan hanya jika kebijakan organisasi mengizinkannya. Jangan mencatat string koneksi, token akses, kata sandi, atau nilai parameter sensitif. Gunakan penghitung diagnostik, pelacakan sumber event, dan panduan pemecahan masalah SqlClient untuk investigasi lebih mendalam.