# SQL Server（SqlClient）命令超时设置速查

## 结论
SqlClient 的**连接字符串不支持**任何「命令超时」关键字：

- ❌ `Command Timeout=` / `CommandTimeout=` → 抛 `System.ArgumentException: 不支持关键字: "command timeout"`
- ✅ 连接字符串里与超时相关的合法键只有：`Connect Timeout`(= `Connection Timeout`, 默认 15)、
  `Load Balance Timeout` / `Connection Lifetime`、`Max Pool Size`、`Min Pool Size`、`Packet Size`
- 命令超时（一条 SQL 执行多久）**只能代码设置**；默认 **30 秒**，`0` = 无限等待（不建议）

## 1. 先修报错：连接串删掉那一项
```diff
- Server=1.2.3.4;Database=App;User Id=sa;Password=***;Connection Timeout=20;Command Timeout=300;Max Pool Size=200
+ Server=1.2.3.4;Database=App;User Id=sa;Password=***;Connect Timeout=20;Max Pool Size=200
```
检查这几处是否都有（都要删）：`appsettings.json` 的 `ConnectionStrings`、`web.config`/`app.config`
的 `<connectionStrings>`、`DesignTimeDbContextFactory`、CI/环境变量。

## 2. EF Core（ASP.NET Core 最常用）
```csharp
// Program.cs
builder.Services.AddDbContext<AppDb>(o =>
    o.UseSqlServer(builder.Configuration.GetConnectionString("Default"),
        sql => sql.CommandTimeout(300)));    // 单位：秒
```
DbContext 内部临时改：
```csharp
context.Database.SetCommandTimeout(300);
```

## 3. Dapper
```csharp
// 全局一次（放在 Program.cs 启动处）
Dapper.SqlMapper.Settings.CommandTimeout = 300;

// 或每次调用
conn.Query<Foo>("select ...", param, commandTimeout: 300);
```

## 4. 原生 ADO.NET
```csharp
using var conn = new SqlConnection(cs);
await conn.OpenAsync();
using var cmd = new SqlCommand(sql, conn) { CommandTimeout = 300 };
```

## 5. SqlSugar
```csharp
db.Ado.CommandTimeOut = 300;     // 注意最后是大写 O；单位秒
```

## 6. FreeSql
```csharp
fsql.Ado.CommandTimeout = 300;
```

## 7. 可选加强：EF Core 全局拦截器（把 300 秒兜到每一处）
```csharp
using System.Data.Common;
using Microsoft.EntityFrameworkCore.Diagnostics;

public sealed class CommandTimeoutInterceptor : DbCommandInterceptor
{
    private readonly int _seconds;
    public CommandTimeoutInterceptor(int seconds = 300) => _seconds = seconds;

    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand c, CommandEventData e, InterceptionResult<DbDataReader> r)
    { c.CommandTimeout = _seconds; return base.ReaderExecuting(c, e, r); }

    public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
        DbCommand c, CommandEventData e, InterceptionResult<DbDataReader> r, CancellationToken ct = default)
    { c.CommandTimeout = _seconds; return base.ReaderExecutingAsync(c, e, r, ct); }

    public override InterceptionResult<object> ScalarExecuting(
        DbCommand c, CommandEventData e, InterceptionResult<object> r)
    { c.CommandTimeout = _seconds; return base.ScalarExecuting(c, e, r); }

    public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(
        DbCommand c, CommandEventData e, InterceptionResult<object> r, CancellationToken ct = default)
    { c.CommandTimeout = _seconds; return base.ScalarExecutingAsync(c, e, r, ct); }

    public override InterceptionResult<int> NonQueryExecuting(
        DbCommand c, CommandEventData e, InterceptionResult<int> r)
    { c.CommandTimeout = _seconds; return base.NonQueryExecuting(c, e, r); }

    public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
        DbCommand c, CommandEventData e, InterceptionResult<int> r, CancellationToken ct = default)
    { c.CommandTimeout = _seconds; return base.NonQueryExecutingAsync(c, e, r, ct); }
}
```
注册：
```csharp
o.UseSqlServer(cs, sql => sql.CommandTimeout(300))
 .AddInterceptors(new CommandTimeoutInterceptor(300));
```

## 8. 验证与善后
- 改连接串后必须**回收应用池 / 重启站点**（连接串在启动时读取）。
- 判断当前是不是命令超时：异常信息为
  `Microsoft.Data.SqlClient.SqlException: Execution Timeout Expired. The timeout period elapsed
  prior to completion of the operation or the server is not responding.`
- 提高超时只是别让慢 SQL 报错，**根因要查**：
  ```sql
  -- 当前正在跑/被阻塞的语句
  SELECT session_id, blocking_session_id, wait_type, wait_time, status, command, total_elapsed_time
  FROM sys.dm_exec_requests WHERE session_id > 50;
  ```
  配合 `DBCC INPUTBUFFER(<session_id>)` 看具体 SQL；锁等待就查索引与事务范围。
- 不要把 `CommandTimeout` 设成 `0`（无限）：一旦库出问题，线程/连接会被长时间占死，线程池耗尽。
