Connettiti a una sorgente dati con Microsoft. Data.SqlClient

SqlConnectionrappresenta una connessione logica a SQL Server, database SQL di Azure, Istanza gestita di SQL di Azure o database SQL in Microsoft Fabric. L'apertura dell'oggetto acquisisce una connessione fisica dal pool di connessioni quando ce n'è una disponibile. Chiudendolo o smaltirlo restituisce quella connessione fisica al pool.

Usa oggetti SqlConnection a vita breve per le unità di lavoro. Non lasciare aperta una sola connessione globale per l'applicazione.

Prerequisites

  • SDK .NET 10 o versione successiva supportata di .NET SDK. L’SDK gira su Windows, Linux e macOS. Compila inoltre la versione .NET Framework di ogni esempio, che gira solo su Windows e necessita di .NET Framework 4.8.1.
  • Un database a cui puoi connetterti in SQL Server, database SQL di Azure, Istanza gestita di SQL di Azure o SQL database in Microsoft Fabric.

Creare il progetto

Microsoft. Data.SqlClient 7.0 e versioni successive necessitano del pacchetto abbinato Microsoft.Data.SqlClient.Extensions.Azure alla versione per le modalità di autenticazione Microsoft Entra ID fornite dal driver, come Active Directory Default. Se la tua app usa solo l'autenticazione SQL o l'autenticazione integrata su Windows, puoi ometterla.

In una cartella vuota, esegui questi comandi:

dotnet new console --framework net10.0 --name SqlClientConnect
cd SqlClientConnect
dotnet add package Microsoft.Data.SqlClient --version 7.1.1
dotnet add package Microsoft.Data.SqlClient.Extensions.Azure --version 7.1.1

I campioni in questo articolo leggono la stringa di connessione dalla SQL_CONNECTION_STRING variabile ambientale. Impostalo prima di eseguire un campione. Per i formati delle stringa di connessione, vedi Connection strings.

Ogni campione, tranne quello della transazione, è un programma completo. Per eseguire uno, sostituisci il contenuto di Program.cs con esso, e poi esegui dotnet run. Il campione della transazione è un frammento del tuo metodo personalizzato. La scheda .NET utilizza istruzioni di alto livello, direttive implicite using e await using impliciti. La scheda .NET Framework usa C# 7.3, che non ha await using, quindi usa using istruzioni invece.

Apri una connessione

Chiama OpenAsync nel codice asincrono o Open nel codice sincrono. Dichiara la connessione con await using o using così viene rilasciata quando il codice esce dall'ambito, anche quando viene lanciata un'eccezione.

Con l'autenticazione integrata di Windows, la connessione utilizza l'identità del processo. Per un'applicazione web o un servizio Windows, quello è il pool di applicazioni o l'account servizio, non l'utente connesso. Per ulteriori informazioni, vedi Scegli un modello di autenticazione.

Questo programma completo apre una connessione, segnala ogni cambiamento di stato e legge due proprietà del server:

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString);
connection.StateChange += (_, e) =>
    Console.WriteLine($"State: {e.OriginalState} to {e.CurrentState}");

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    SELECT CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
           CAST(SERVERPROPERTY('EngineEdition') AS int);
    """,
    connection);

await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    await reader.ReadAsync();
    Console.WriteLine($"Edition: {reader.GetString(0)}");
    Console.WriteLine($"EngineEdition: {reader.GetInt32(1)}");
}

Rispetto a SQL Server 2025 edizione Enterprise, l'output è:

State: Closed to Open
Edition: Enterprise Edition: Core-based Licensing (64-bit)
EngineEdition: 3
State: Open to Closed

L'ultima linea appare quando la connessione viene dismessa alla fine del programma.

Chiudere e rilasciare le connessioni

Chiudi sempre una connessione quando hai finito di usarla, così la connessione fisica torna nel pool. Le istruzioni using e await using eliminano la connessione quando il codice esce dal blocco, incluso quando viene lanciata un'eccezione non gestita. Puoi anche chiamare Close o Dispose direttamente con la chiamata. Close e Dispose hanno lo stesso effetto sulla connessione fisica.

Segui queste regole:

  • Eliminare i reader e i command prima della connessione che li contiene.
  • Non affidarti alla garbage collection per riportare la connessione al pool. Una connessione che esce dall'ambito senza essere chiusa non viene restituita prontamente, e il pool può rimanere senza connessioni.
  • Non chiamare Close o Dispose su una connessione, un lettore o qualsiasi altro oggetto gestito in un finalizzatore di classe. Un finalizer dovrebbe rilasciare solo le risorse non gestite che la sua classe possiede direttamente. Per maggiori informazioni, vedi garbage collection.
  • Dopo Close, puoi richiamare di nuovo Open sullo stesso oggetto. Dopo Dispose, crea un nuovo oggetto.

Con il pooling abilitato, chiudendo una connessione, questa viene restituita al pool senza chiudere la connessione di rete. Il pool resetta lo stato della sessione quando la connessione viene riutilizzata. Per maggiori informazioni, vedi SQL Server connection pooling.

Usare le API asincrone

Usa chiamate asincrone per il lavoro su database legato alla rete in server web, servizi, interfacce utente e lavoratori:

  • OpenAsync(cancellationToken)
  • ExecuteNonQueryAsync(cancellationToken)
  • ExecuteReaderAsync(cancellationToken)
  • ExecuteScalarAsync(cancellationToken)
  • ReadAsync(cancellationToken)

Non iniziare un'altra operazione su una connessione, un comando o un lettore prima che l'operazione asincrona attuale sia terminata.

Applica la cancellazione e i timeout

Propaga il CancellationToken del chiamante in ogni chiamata asincrona al database. L'annullamento chiede al driver di fermare le operazioni in sospeso, ma non è garantito che il completamento sia immediato. Continua a usare timeout di connessione e di comando limitati.

Questi controlli hanno ambiti separati:

Controllo Scope
Connect Timeout Creazione della connessione, ovvero l'attesa di una connessione dal pool
SqlCommand.CommandTimeout Esecuzione di un comando
CancellationToken Cancellazione richiesta dal chiamante di un'operazione asincrona

Un timeout o un annullamento non dimostrano che il server abbia annullato un'operazione. Utilizzare una transazione quando più modifiche devono effettuare un commit o tornare indietro come un'unica unità, e prendere decisioni di ritentativo basate sull'idempotenza e sull'esito della transazione.

Traccia stato della connessione

La State proprietà restituisce una istantanea dall'enumerazione ConnectionState .

State Meaning
Closed La connessione logica non è aperta.
Connecting È in corso un'operazione aperta.
Open La connessione logica è aperta.

L'evento StateChange si attiva quando lo stato cambia. Il relativo argomento StateChangeEventArgs ha due proprietà:

  • OriginalState è lo stato prima del cambiamento.
  • CurrentState è lo stato dopo il cambiamento.

Il driver riporta le transizioni da Closed a Open e da Open a Closed, come mostra il campione in Open a connection. Non fare affidamento sull'osservazione di Executing, Fetching o Broken come fasi del ciclo di vita dell'applicazione.

Non usare State come controllo dello stato prima di ogni comando. La rete può non funzionare dopo qualsiasi verifica. Esegui l'operazione e gestisci l'eccezione risultante. Usa StateChange per la diagnostica, non per coordinare il lavoro concorrente.

Ricevere messaggi informativi

L'evento InfoMessage recapita i messaggi che il server invia senza sollevare un errore. Questi messaggi includono PRINT output, RAISERROR messaggi con gravità pari o inferiore a 10 e avvisi del server. Errori con una gravità di 11 o superiore generano invece un SqlException. Per i livelli di gravità, vedere gravità degli errori del Motore di database.

Il gestore di eventi riceve un SqlInfoMessageEventArgs oggetto. La sua Errors proprietà contiene una collezione di SqlError oggetti. Ognuno include il numero del messaggio, il testo, la classe di gravità, la procedura e il numero della riga.

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString);
connection.InfoMessage += (_, e) =>
{
    foreach (SqlError error in e.Errors)
    {
        Console.WriteLine(
            $"Message {error.Number}, severity {error.Class}: {error.Message}");
    }
};

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    PRINT 'Starting the batch.';
    RAISERROR('Processed %d rows.', 10, 1, 42);
    """,
    connection);

await command.ExecuteNonQueryAsync();

L'output è il seguente:

Message 0, severity 0: Starting the batch.
Message 50000, severity 0: Processed 42 rows.

Il server riporta una RAISERROR gravità di 10 come classe 0, quindi SqlError.Class mostra 0 per entrambi i messaggi.

Gestire gli errori dell'utente come messaggi informativi

Per impostazione predefinita, il driver segnala un errore con gravità pari o superiore a 11, lanciando un SqlException. Quell'eccezione non significa che il server abbia fermato il batch. Molti errori, incluso RAISERROR quelli con una gravità di 16, permettono al server di eseguire le istruzioni rimanenti, così che le istruzioni successive possano modificare i dati anche quando il tuo codice gestisce l'eccezione. Per ricevere questi errori come messaggi invece che come eccezioni, impostare FireInfoMessageEventOnUserErrors su true. Il driver poi invia errori con una gravità di 16 o inferiore all'evento InfoMessage invece di lanciare. Questa proprietà modifica il modo in cui il driver segnala gli errori. Non cambia quali istruzioni il server esegue.

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    FireInfoMessageEventOnUserErrors = true,
};
connection.InfoMessage += (_, e) =>
{
    foreach (SqlError error in e.Errors)
    {
        Console.WriteLine(
            $"Message {error.Number}, severity {error.Class}: {error.Message}");
    }
};

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    RAISERROR('Row 7 failed validation.', 16, 1);
    SELECT 'The batch continued.';
    """,
    connection);

await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    do
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine(reader.GetString(0));
        }
    }
    while (await reader.NextResultAsync());
}

L'output è il seguente:

Message 50000, severity 16: Row 7 failed validation.
The batch continued.

Errori con una gravità pari o superiore a 17 lanciano comunque un SqlException, indipendentemente da questa impostazione. Quando abiliti questa proprietà, il tuo handler è responsabile del rilevamento degli errori. Il codice che si aspetta un'eccezione per ogni errore non ne vede una.

Segnala l'identità dell'applicazione

A partire da Microsoft. Data.SqlClient 7.1.0, un framework, uno strumento o altro middleware può segnalare quale applicazione ha aperto una connessione. Imposta RegisteredApplication su un valore RegisteredApplication prima di aprire la connessione. Il driver invia il valore al server una volta, durante l'accesso.

Segui queste regole:

  • Imposta il valore attraverso la proprietà. Non esiste alcuna parola chiave della stringa di connessione per essa.
  • Imposta il valore prima di aprire la connessione. Impostandolo mentre la connessione è in apertura o aperta genera un InvalidOperationException.
  • Considera il valore come telemetria. Il client fornisce il valore e può segnalare qualsiasi identificatore, quindi il server non lo autentica.
  • Aspettati che le connessioni in pool riportino l'identità originale. Il valore non fa parte della chiave del pool. Una connessione servita dal pool riporta l'identità della connessione logica che ha causato la creazione della connessione fisica.

Caution

Non usare mai RegisteredApplication per l'autorizzazione o altre decisioni di sicurezza. Qualsiasi cliente può segnalare qualsiasi identificatore.

Scegli un identificatore

Il valore predefinito è Unknown, che è 0. L'enumerazione ha un tipo sottostante di ushort, quindi gli identificatori vanno da 0 fino a 0xFFFF.

Range Utilizza
0x0001 attraverso 0x7FFF Applicazioni definite da Microsoft. I membri dell'enumerazione nominata utilizzano questo intervallo.
0x8000 attraverso 0xBFFF Riservato per uso su piccola scala.
0xC000 attraverso 0xFFFF Uso pubblico e da parte degli sviluppatori.

I membri nominati sono EntityFrameworkCore, SemanticKernel, ManagementStudio, SqlManagementObjects, DataTierApplicationFramework, SqlToolsService, AspNetCoreDistributedSqlServerCache, AzureFunctionsSqlExtension, OrleansAdoNet, DurableTaskSqlServer, EntityFramework, SqlPackage e DataApiBuilder. Per segnalare un identificatore che non ha un membro denominato, occorre eseguire il cast del numero a RegisteredApplication.

Il RegisteredApplication tipo e la proprietà non sono conformi a CLS perché l'enumerazione usa ushort. Il codice C# può usarli direttamente.

Imposta l'identificatore

Questo programma riporta un identificatore dall'intervallo di sviluppatore e poi mostra cosa succede quando il codice cambia valore dopo l'apertura della connessione:

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    RegisteredApplication = (RegisteredApplication)0xC001,
};

await connection.OpenAsync();
Console.WriteLine(
    $"Reported identifier: 0x{(ushort)connection.RegisteredApplication:X4}");

try
{
    connection.RegisteredApplication = RegisteredApplication.Unknown;
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

L'output è il seguente:

Reported identifier: 0xC001
InvalidOperationException: Not allowed to change the 'RegisteredApplication' property. The connection's current state is open.

Comprendere gli effetti del pooling

Il driver segnala l'identificatore solo quando crea una nuova connessione fisica. Con il pooling abilitato:

  • Una connessione che riutilizza una connessione fisica aggregata riporta l'identità della connessione che ha creato la connessione fisica, non la propria.
  • Collegamenti fisici che il pool apre in background per soddisfare Min Pool Size report Unknown.

Se una stringa di connessione serve più identità, considera il valore riportato come approssimativo. Disabilita il pooling per quella stringa di connessione quando serve l'attribuzione esatta e misura prima il costo extra di connessione.

SqlConnection.Clone copia il valore RegisteredApplication nella nuova connessione.

Non condividere una connessione contemporaneamente

SqlConnection, SqlCommand, SqlDataReader, e SqlTransaction non supportano l'uso concorrente da parte di più thread. Assegnare a ogni operazione concorrente la propria connessione e lasciare che il pool di connessione riutilizzi le connessioni fisiche.

I Multiple Active Result Set (MARS) consentono più lotti attivi su una sola connessione in scenari supportati. Non rende gli oggetti SqlClient thread-safe e aggiunge regole di sessione e transazione. Lascialo disabilitato a meno che un'operazione non ne abbia bisogno specificamente.

Non registrare un open SqlConnection come singleton nell'iniezione di dipendenza. Registra la stringa di connessione, un oggetto di opzioni immutabile o una funzione factory che crea una nuova connessione.

Usa le transazioni in modo consapevole

Una transazione locale è associata alla relativa connessione. Ogni comando nella transazione deve utilizzare quella connessione e impostarne Transaction la proprietà.

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);

await using SqlTransaction transaction =
    (SqlTransaction)await connection.BeginTransactionAsync(cancellationToken);

using var command = new SqlCommand(sql, connection, transaction);
command.Parameters.Add(
    new SqlParameter("@value", SqlDbType.Int) { Value = value });
await command.ExecuteNonQueryAsync(cancellationToken);

await transaction.CommitAsync(cancellationToken);

Se l'operazione fallisce prima del commit, l'eliminazione della transazione ne esegue il rollback. Mantieni le transazioni brevi. Non fare chiamate di rete, interazioni con l'utente o calcoli non correlati mentre una transazione nel database contiene blocchi.

Quando System.Transactions.Transaction.Current è attivo, Open e OpenAsync vengono registrati automaticamente per impostazione predefinita. Impostare Enlist=false solo quando l'operazione deve restare all'esterno della transazione ambientale.

Misurare una connessione logica

Imposta StatisticsEnabled su true per raccogliere le statistiche del driver per un oggetto SqlConnection:

using System.Collections;
using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    StatisticsEnabled = true,
};

await connection.OpenAsync();
connection.ResetStatistics();

await using var command = new SqlCommand("SELECT name FROM sys.databases;", connection);
await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    while (await reader.ReadAsync())
    {
    }
}

IDictionary statistics = connection.RetrieveStatistics();
Console.WriteLine($"ServerRoundtrips: {statistics["ServerRoundtrips"]}");
Console.WriteLine($"SelectRows: {statistics["SelectRows"]}");
Console.WriteLine($"BytesReceived > 0: {(long)statistics["BytesReceived"]! > 0}");

L'output mostra un'andata e ritorno. Il conteggio delle righe dipende dal numero di database presenti sul server:

ServerRoundtrips: 1
SelectRows: 14
BytesReceived > 0: True

RetrieveStatistics restituisce una istantanea. ResetStatistics Avvia un nuovo confine di misura. Imposta StatisticsEnabled su false per interrompere la raccolta. I valori raccolti finora restano disponibili. Le statistiche sono per oggetto di connessione e aggiungono overhead, quindi abilitali per una diagnosi mirata invece che per ogni richiesta di produzione.

Gestire gli errori di connessione

Intercetta SqlException in un punto di confine che possa registrare, tradurre o ritentare l’operazione dopo l’errore. Registrazione:

  • Number
  • State
  • Class
  • ClientConnectionId
  • Il nome dell'operazione e gli identificatori configurati di server e database

Non registrare la stringa di connessione, la password, il client secret o il token di accesso.

Elimina una connessione rotta. Il pool rimuove le connessioni fisiche non valide quando le rileva. Se una credenziale, un token, un certificato, un DNS target o un server cambiato, correggi la configurazione prima di riprovare.

Usa la logica di ritentazione limitata solo per i fallimenti transitori. Il ritentativo di apertura iniziale, il recupero di una connessione inattiva e il ritentativo del comando sono meccanismi diversi. Per maggiori informazioni, consulta Logica di ritenti configurabile e le opzioni di recupero connessione in Opzioni di connessione.