Conéctate a una fuente de datos con Microsoft. Data.SqlClient

SqlConnection representa una conexión lógica a SQL Server, Azure SQL Database, Azure SQL Managed Instance o SQL database en Microsoft Fabric. Abrir el objeto obtiene una conexión física del conjunto de conexiones cuando hay una disponible. Cerrarlo o deshacerse de ella devuelve esa conexión física al pool.

Utiliza objetos SqlConnection de vida corta para unidades de trabajo. No dejes una sola conexión global abierta para la aplicación.

Prerequisites

  • El SDK de .NET 10 o un SDK de .NET posterior compatible. El SDK funciona en Windows, Linux y macOS. También compila la versión de .NET Framework de cada ejemplo, que solo funciona en Windows y necesita .NET Framework 4.8.1.
  • Una base de datos a la que puedas conectarte en SQL Server, Azure SQL Database, Azure SQL Managed Instance o base de datos SQL en Microsoft Fabric.

Creación del proyecto

Microsoft. Data.SqlClient 7.0 y versiones posteriores necesitan el paquete de versión compatible Microsoft.Data.SqlClient.Extensions.Azure para los modos de autenticación Microsoft Entra ID proporcionados por el controlador, como Active Directory Default. Si tu app solo usa autenticación SQL o autenticación integrada en Windows, puedes omitirla.

En una carpeta vacía, ejecuta estos comandos:

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

Los ejemplos de este artículo leen la cadena de conexión de la variable de entorno SQL_CONNECTION_STRING. Ponlo antes de ejecutar una muestra. Para los formatos de cadena de conexión, véase Connection strings.

Cada muestra excepto la muestra de transacción es un programa completo. Para ejecutar uno, reemplaza el contenido de Program.cs por él y luego ejecuta dotnet run. La muestra de transacción es un fragmento para su propio método. La pestaña .NET utiliza instrucciones de nivel superior, directivas using implícitas using y await using. La pestaña .NET Framework usa C# 7.3, que no tiene await using, así que usa sentencias using en su lugar.

Abre una conexión

Llame a OpenAsync en código asincrónico o a Open en código síncrono. Declara la conexión con await using o using para que se libere cuando el código salga del alcance, incluso cuando se lance una excepción.

Con la autenticación integrada de Windows, la conexión utiliza la identidad del proceso. Para una aplicación web o un servicio de Windows, eso es el grupo de aplicaciones o la cuenta de servicio, no el usuario que ha iniciado sesión. Para más información, consulta Elegir un patrón de autenticación.

Este programa completo abre una conexión, informa de cada cambio de estado y lee dos propiedades del servidor:

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

Frente a SQL Server 2025 edición Enterprise, la salida es:

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

La última línea aparece cuando la conexión se libera al final del programa.

Cerrar y liberar conexiones

Cierra siempre una conexión cuando termines de usarla para que la conexión física vuelva al pool de conexiones. Las sentencias using y await using liberan la conexión cuando el código sale del bloque, incluyendo cuando se lanza una excepción no gestionada. También puedes llamar Close o Dispose directamente. Close y Dispose tienen el mismo efecto en la conexión física.

Siga estas reglas:

  • Elimine los lectores y comandos antes que la conexión que los posee.
  • No dependas de la recolección de basura para devolver la conexión al grupo de conexiones. Una conexión que se sale del alcance sin cerrarse no se devuelve rápidamente, y el pool puede quedarse sin conexiones.
  • No llames a Close ni a Dispose en una conexión, un lector o cualquier otro objeto gestionado en un finalizador de clase. Un finalizador debería liberar solo recursos no gestionados que su clase posea directamente. Para más información, véase Recolección de elementos no utilizados.
  • Después de Close, puedes llamar a Open de nuevo en el mismo objeto. Después de Dispose, crea un nuevo objeto.

Con el pooling activado, cerrar una conexión la devuelve al pool sin cerrar la conexión de red. El pool reinicia el estado de la sesión cuando se reutiliza la conexión. Para más información, consulte SQL Server connection pooling.

Usar API asincrónicas

Utiliza llamadas asíncronas para trabajo de bases de datos vinculadas a red en servidores web, servicios, interfaces de usuario y trabajadores:

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

No inicies otra operación en una conexión, comando o lector antes de que termine la operación asíncrona actual.

Solicita cancelaciones y tiempos de espera

Pasar el CancellationToken del llamador en todas las llamadas asíncronas a la base de datos. La cancelación pide al controlador que detenga los trabajos pendientes, pero no se garantiza que se complete de inmediato. Sigue usando tiempos de espera limitados para la conexión y los comandos.

Estos controles tienen ámbitos separados:

Control Alcance
Connect Timeout Establecimiento de conexión, o la espera de una conexión del pool
SqlCommand.CommandTimeout Ejecución de un comando
CancellationToken Cancelación solicitada por el llamante de una operación asíncrona

Un tiempo de espera o cancelación no prueba que el servidor haya revertido una operación. Utiliza una transacción cuando varios cambios deban confirmarse o revertirse como una única unidad, y toma decisiones sobre los reintentos en función de la idempotencia de la operación y del resultado de la transacción.

Estado de la conexión

La State propiedad devuelve una instantánea de la ConnectionState enumeración.

Estado Meaning
Closed La conexión lógica no está abierta.
Connecting Se está llevando a cabo una operación abierta.
Open La conexión lógica está abierta.

El StateChange evento se activa cuando cambia el estado. El argumento StateChangeEventArgs tiene dos propiedades:

  • OriginalState es el estado antes del cambio.
  • CurrentState es el estado posterior al cambio.

El controlador informa de las transiciones de Closed a Open y de Open a Closed, como muestra el ejemplo de Open a connection. No confíes en observar Executing, Fetching ni Broken como fases del ciclo de vida de la aplicación.

No uses State como comprobación de estado antes de cada comando. La red puede fallar tras cualquier comprobación. Ejecuta la operación y gestiona la excepción resultante. Use StateChange para diagnósticos, no para coordinar trabajos concurrentes.

Recibir mensajes informativos

El InfoMessage evento entrega mensajes que el servidor envía sin generar ningún error. Estos mensajes incluyen PRINT la salida, RAISERROR mensajes con una gravedad de 10 o menos, y advertencias de servidor. Los errores con una gravedad de 11 o más generan un SqlException en su lugar. Para los niveles de gravedad, consulte gravedades de error del Motor de base de datos.

El gestor de eventos recibe un SqlInfoMessageEventArgs objeto. La propiedad Errors contiene una colección de objetos SqlError. Cada uno incluye el número del mensaje, el texto, la clase de severidad, el procedimiento y el número de línea.

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

La salida es la siguiente:

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

El servidor informa de una RAISERROR gravedad de 10 como clase 0, por lo que SqlError.Class muestra 0 para ambos mensajes.

Gestionar los errores del usuario como mensajes informativos

Por defecto, el controlador informa de un error con gravedad de 11 o más al lanzar un SqlException. Esa excepción no significa que el servidor haya interrumpido el procesamiento por lotes. Muchos errores, incluso RAISERROR con una gravedad de 16, permiten que el servidor ejecute las sentencias restantes, por lo que las sentencias posteriores pueden cambiar datos incluso cuando tu código detecta la excepción. Para recibir estos errores como mensajes en lugar de excepciones, se fija FireInfoMessageEventOnUserErrors en true. El controlador entonces envía errores de gravedad de 16 o menos al evento InfoMessage en lugar de generar una excepción. Esta propiedad cambia la forma en que el controlador informa de los errores. No cambia qué sentencias ejecuta el servidor.

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

La salida es la siguiente:

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

Los errores con una gravedad de 17 o más siguen generando un SqlException, independientemente de esta configuración. Cuando activas esta propiedad, tu controlador es responsable de detectar fallos. El código que espera una excepción por cada error no ve ninguna.

Informa de la identidad de la aplicación

Empezando por Microsoft.Data.SqlClient 7.1.0, un framework, herramienta u otro middleware puede informar qué aplicación abrió una conexión. Configura RegisteredApplication con un valor RegisteredApplication antes de abrir la conexión. El controlador envía el valor al servidor una vez, durante el inicio de sesión.

Siga estas reglas:

  • Fija el valor a través de la propiedad. No existe ninguna palabra clave de la cadena de conexión para ello.
  • Establece el valor antes de abrir la conexión. Configurarlo mientras la conexión está abriéndose o abierta genera un InvalidOperationException.
  • Trata el valor como telemetría. El cliente proporciona el valor y puede reportar cualquier identificador, por lo que el servidor no lo autentica.
  • Se espera que las conexiones agrupadas reporten la identidad original. El valor no forma parte de la clave del pool. Una conexión servida desde el pool informa de la identidad de la conexión lógica que causó la creación de la conexión física.

Caution

No utilices RegisteredApplication para autorización ni para ninguna otra decisión de seguridad. Cualquier cliente puede reportar cualquier identificador.

Elige un identificador

El valor por defecto es Unknown, que es 0. La enumeración tiene un tipo subyacente de ushort, por lo que los identificadores van desde 0 hasta 0xFFFF.

Range Uso
De 0x0001 a 0x7FFF Aplicaciones definidas por Microsoft. Los miembros de enumeración nombrados utilizan este rango.
De 0x8000 a 0xBFFF Reservado para uso a pequeña escala.
De 0xC000 a 0xFFFF Uso público y de desarrolladores.

Los miembros nombrados son EntityFrameworkCore, SemanticKernel, ManagementStudio, SqlManagementObjects, DataTierApplicationFramework, SqlToolsService, AspNetCoreDistributedSqlServerCache, EntityFramework, DurableTaskSqlServer, SqlPackage, AzureFunctionsSqlExtension, OrleansAdoNet y DataApiBuilder. Para reportar un identificador que no tenga un miembro con nombre, convierte el número en RegisteredApplication.

El RegisteredApplication tipo y la propiedad no cumplen con CLS porque la enumeración usa ushort. El código C# puede usarlos directamente.

Establecer el identificador

Este programa informa de un identificador del rango de desarrollador y luego muestra qué ocurre cuando el código cambia el valor tras la apertura de la conexión:

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

La salida es la siguiente:

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

Comprender los efectos de la agrupación

El controlador solo informa del identificador cuando crea una nueva conexión física. Con el pooling activado:

  • Una conexión que reutiliza una conexión física agrupada informa de la identidad de la conexión que creó la conexión física, no la suya propia.
  • Conexiones físicas que el grupo abre en segundo plano para satisfacer Min Pool Size lo indicado Unknown.

Si una cadena de conexión se usa para varias identidades, trata el valor reportado como aproximado. Desactiva el pooling para esa cadena de conexión cuando necesites la atribución exacta y mide primero el coste extra de conexión.

SqlConnection.Clone copia el RegisteredApplication valor a la nueva conexión.

No compartas una conexión simultáneamente

SqlConnection, SqlCommand, SqlDataReader, y SqlTransaction no soportan el uso simultáneo por múltiples hilos. Asignar a cada operación concurrente su propia conexión y dejar que la agrupación de conexiones reutilice las conexiones físicas.

Múltiples Conjuntos de Resultados Activos (MARS) permite múltiples lotes activos en una misma conexión en escenarios compatibles. No hace que los objetos SqlClient sean seguros para hilos, y añade reglas de sesión y transacciones. Déjalo desactivado a menos que una operación lo requiera específicamente.

No registres un open SqlConnection como un singleton en la inyección de dependencia. Registre la cadena de conexión, un objeto de opciones inmutable o una fábrica que crea una nueva conexión.

Usa las transacciones de forma deliberada

Una transacción local pertenece a su conexión. Cada comando en la transacción debe usar esa conexión y establecer su Transaction propiedad.

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

Si la operación falla antes del commit, al desechar la transacción se revierte. Mantén las transacciones cortas. No hagas llamadas de red, interacción con el usuario ni cálculos no relacionados mientras una transacción de base de datos tiene bloqueos.

Cuando System.Transactions.Transaction.Current está activo, Open y OpenAsync se registran automáticamente de forma predeterminada. Se establece Enlist=false solo cuando la operación debe permanecer fuera de la transacción ambiental.

Mida una conexión lógica

Configura StatisticsEnabled para true recopilar estadísticas del controlador para un SqlConnection objeto:

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

La salida muestra un ciclo completo. El recuento de filas depende del número de bases de datos en el servidor:

ServerRoundtrips: 1
SelectRows: 14
BytesReceived > 0: True

RetrieveStatistics devuelve una instantánea. ResetStatistics Inicia un nuevo límite de medición. Configura StatisticsEnabled en false para dejar de recopilar. Los valores recogidos hasta ahora siguen disponibles. Las estadísticas son por objeto de conexión y añaden sobrecarga, así que habilitalas para un diagnóstico dirigido en lugar de cada solicitud de producción.

Manejar fallos de conexión

Atrape SqlException en un límite que pueda registrar, traducir o intentar repetir el fallo. Grabar:

  • Number
  • State
  • Class
  • ClientConnectionId
  • El nombre de la operación y los identificadores configurados del servidor y la base de datos

No registres la cadena de conexión, la contraseña, el secreto del cliente ni el token de acceso.

Deshágase de una conexión rota. El pool elimina conexiones físicas inválidas cuando las detecta. Si una credencial, token, certificado, destino DNS o servidor cambió, corrige la configuración antes de intentarlo de nuevo.

Utiliza lógica de reintento acotada solo para fallos transitorios. El reintento en la apertura inicial, la recuperación de conexiones inactivas y el reintento de comandos son mecanismos diferentes. Para más información, consulte Lógica de reintento configurable y las opciones de recuperación de conexión en Opciones de conexión.