使用 Microsoft.Data.SqlClient 连接到数据源

SqlConnection表示与SQL Server、Azure SQL或其他支持的SQL Server兼容端点的一种逻辑连接。 打开该对象时,如果连接池中有可用连接,就会从连接池获取一个物理连接。 关闭或丢弃它会恢复到池的物理连接。

使用 SqlConnection 短命的对象作为工作单元。 不要为应用开启一个全局连接。

构建连接配置

从应用程序的配置系统加载连接字符串。 当代码需要验证或添加设置时使用 SqlConnectionStringBuilder :

string configuredConnectionString =
    configuration.GetConnectionString("Orders")
    ?? throw new InvalidOperationException(
        "Connection string 'Orders' wasn't configured.");

var builder = new SqlConnectionStringBuilder(configuredConnectionString)
{
    ApplicationName = "Orders.Api",
};

从 builder.ConnectionString 创建 SqlConnection。 不要把用户输入串接到字符串里。 关于认证模式、安全存储和语法,请参见 连接字符串。

打开并释放连接

在同步代码中调用 Open,或在异步代码中调用 OpenAsync。 为每个独立操作开启一个新的逻辑连接:

public static async Task<string?> LoadOrderStatusAsync(
    string connectionString,
    int orderId,
    CancellationToken cancellationToken)
{
    await using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync(cancellationToken);

    const string sql = """
        SELECT Status
        FROM Sales.Orders
        WHERE OrderId = @orderId;
        """;

    using var command =
        new SqlCommand(sql, connection) { CommandTimeout = 30 };
    command.Parameters.Add(
        new SqlParameter("@orderId", SqlDbType.Int) { Value = orderId });

    object? value =
        await command.ExecuteScalarAsync(cancellationToken);
    return value is null or DBNull ? null : (string)value;
}

await using该语句会在成功、出错或取消时释放该连接。 启用池化后,处理通常会重置并返回物理连接,而不是关闭其网络接口。

先释放读取器和命令,再释放拥有它们的连接。 不要依赖垃圾回收或终结器将连接归还到连接池中。

使用异步 API

在网络服务器、服务、用户界面和工作者中,使用异步调用进行网络绑定数据库工作:

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

你不需要 Asynchronous Processing=true。 Microsoft。Data.SqlClient 4.0及以后版本不支持那个连接字符串关键字。

在当前异步操作结束前,不要对连接、命令或读卡器启动另一个操作。

应用取消和超时

在每一次异步数据库调用中传递调用方的 CancellationToken。 取消要求服务提供者停止待完成的工作,但完成并不保证能立即完成。 继续使用有边界的连接超时和命令超时。

这些控制有不同的范围:

控制 Scope
Connect Timeout 连接建立或等待池化连接
SqlCommand.CommandTimeout 执行一条命令
CancellationToken 调用方请求取消异步操作

超时或取消并不意味着服务器已回滚该操作。 当多个变更必须作为一个单元提交或回滚时,使用事务,并根据操作的幂等性和事务结果做出重试决策。

理解连接状态

State 属性从 ConnectionState 枚举中返回快照。

State Meaning
Closed 逻辑连接未打开。
Connecting 一场公开行动正在进行中。
Open 逻辑联系是开放的。

不要在每次下指令前都用 State 来做健康检查。 网络在任何检查后都可能失败。 执行操作并处理由此产生的异常。

驱动程序通常报告从闭合到开启以及从开启到闭合的转换。 不要依赖于将Executing、Fetching或Broken视为应用生命周期阶段。

StateChange 事件报告状态转换。 InfoMessage 事件报告的信息消息和服务器警告不会变成异常。 请将这些事件用于诊断,而不要用于协调并发工作。

不要同时共享连接

SqlConnection SqlCommand, , SqlDataReader, , SqlTransaction 不支持多个线程同时使用。 给每个并发操作单独的连接,让连接池重用物理连接。

多激活结果集(MARS)允许在支持的场景下,在同一连接上同时运行多个活跃批次。 它并不能使 SqlClient 对象具备线程安全性,并且还引入了会话和事务规则。 除非某个操作特别需要,否则保持禁用。

不要在依赖注入中将开放泛型 SqlConnection 注册为单例。 注册连接字符串、不可变的选项对象,或用于创建新连接的工厂。

有意使用事务

本地事务属于其连接。 事务中的每个命令都必须使用该连接并设置其 Transaction 属性。

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

如果操作在 CommitAsync 之前失败,释放该事务会将其回滚。 保持交易简短。 数据库事务持有锁时,不要做网络调用、用户交互或无关计算。

当 System.Transactions.Transaction.Current 处于活动状态时,Open 和 OpenAsync 默认会自动加入。 仅当该操作必须位于当前事务上下文之外时,才设置 Enlist=false。

测量一个逻辑连接

将 StatisticsEnabled 设置为 true,以收集一个 SqlConnection 对象的提供程序统计信息:

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

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

using var command = new SqlCommand(sql, connection);
await command.ExecuteNonQueryAsync(cancellationToken);

System.Collections.IDictionary statistics =
    connection.RetrieveStatistics();
long roundTrips =
    Convert.ToInt64(statistics["ServerRoundtrips"]);

RetrieveStatistics 返回快照。 ResetStatistics 开始新的测量边界。 将 StatisticsEnabled=false 设置为停止收集;到目前为止收集的值仍然可用。 统计数据是针对连接对象的,增加了开销,所以应启用它们用于有针对性诊断,而不是每次生产请求。

对于全进程的池和连接测量,可以使用 SqlClient 诊断计数器。

处理连接失败

在可记录、转换或重试失败操作的边界处捕获 SqlException。 记录:

  • Number
  • State
  • Class
  • ClientConnectionId
  • 操作名称以及配置的服务器和数据库标识符

不要记录连接字符串、密码、客户端机密或访问令牌。

释放已断开的连接。 池检测到无效物理连接时会删除它们。 如果凭证、令牌、证书、DNS目标或服务器发生变化,请在重试前修正配置。

仅对瞬态故障使用有界重试逻辑。 初始开放重试、空闲连接恢复和命令重试是不同的机制。 参见 可配置重试逻辑。