Microsoft.Data.SqlClient 中的連接字串建構器

用 SqlConnectionStringBuilder 來建立和編輯透過型別屬性來建立和編輯連線字串。 它繼承自 DbConnectionStringBuilder。 只要程式碼新增或改變值,就用它,而不是直接連接字串。

建築商:

  • 將每個關鍵字以具型別的屬性形式公開,例如 ConnectTimeout 為 int,而 Encrypt 為 SqlConnectionEncryptOption。
  • 當您設定時,它會以 ArgumentException 拒絕未知關鍵字和許多超出範圍的值。
  • 將 Server 和 uid 等同義詞映射到如 Data Source 和 User ID 的標準關鍵字。
  • 將包含分號、引號或前後空格的值加上引號。

關於關鍵字列表,請參見 「連線選項」。 關於文字格式,請參見 解析規則。

每個樣本都是完整的程式。 .NET Framework 分頁使用了 C# 7.3。 若要建立執行樣本的專案,請參閱「建立專案」。

防止 連接字串 注入

當你的程式碼在 連接字串 插入不受信任的文字時,該文字中的分號可以新增關鍵字或替換先前的關鍵字,因為最後一個值會勝出。 建構器會把整個值存在同一個屬性裡並加上引號,這樣文字就不會變成另一個關鍵字。

此程式設定 InitialCatalog 為一個嘗試關閉池化的值:

using Microsoft.Data.SqlClient;

// Simulates a value that came from a user or another untrusted source.
string databaseFromUser = "Orders;Pooling=false";

var builder = new SqlConnectionStringBuilder
{
    DataSource = "tcp:sql.example.com,1433",
    InitialCatalog = databaseFromUser,
    IntegratedSecurity = true,
    Encrypt = SqlConnectionEncryptOption.Strict,
};

Console.WriteLine(builder.ConnectionString);
Console.WriteLine($"Pooling: {builder.Pooling}");

輸出為:

Data Source=tcp:sql.example.com,1433;Initial Catalog="Orders;Pooling=false";Integrated Security=True;Encrypt=Strict
Pooling: True

建置器引用該值,而 Pooling 則保持 True 不變。 驅動程式會將 Orders;Pooling=false 作為資料庫名稱傳送至伺服器。

加上引號可以阻止關鍵字注入。 它不會判定某個值是否被允許。 使用前請先將不受信任的輸入與允許的伺服器或資料庫清單進行比對。

完成基本連線字串(base 連接字串)

將連接字串中不會改變的部分,例如伺服器和資料庫,儲存在設定中。 執行時加入秘密值和工作負載特定值。 把基礎字串傳給 builder 建構函式,然後設定屬性。

這個程式會從不同的環境變數讀取基底字串和密碼,使用 SQL 驗證進行連線,並確認連線開啟後密碼不可見。

using Microsoft.Data.SqlClient;

// The base string holds no secret. The password comes from a separate secret source.
string baseConnectionString =
    Environment.GetEnvironmentVariable("SQL_BASE_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_BASE_CONNECTION_STRING environment variable.");
string password =
    Environment.GetEnvironmentVariable("SQL_PASSWORD")
    ?? throw new InvalidOperationException(
        "Set the SQL_PASSWORD environment variable.");

var builder = new SqlConnectionStringBuilder(baseConnectionString)
{
    Password = password,
    ApplicationName = "Orders.Worker",
};

await using var connection = new SqlConnection(builder.ConnectionString);
await connection.OpenAsync();

await using var command = new SqlCommand("SELECT APP_NAME();", connection);
Console.WriteLine($"APP_NAME(): {await command.ExecuteScalarAsync()}");

var opened = new SqlConnectionStringBuilder(connection.ConnectionString);
Console.WriteLine($"Password visible after open: {opened.ContainsKey("Password") && opened.Password.Length > 0}");

設 SQL_BASE_CONNECTION_STRING 為 Server=tcp:<server>,1433;Database=<database>;User ID=<user>;Encrypt=true 等值。 輸出為:

APP_NAME(): Orders.Worker
Password visible after open: False

Persist Security Info 預設為 false,因此驅動程式會在連線開啟後從 SqlConnection.ConnectionString 移除密碼。 保留預設。

偏好不需要密碼的認證方式,例如 Windows 整合認證或 Microsoft Entra ID 受控識別。 欲了解更多資訊,請參閱 Microsoft Entra ID 認證與 Protect 連線資訊。

讀取並更改數值

建構器同時也是字典。 索引器 ContainsKey、Remove 和 TryGetValue 接受關鍵字或其任何同義詞。

using Microsoft.Data.SqlClient;

var b = new SqlConnectionStringBuilder("Server=sql.example.com;Database=Orders;uid=app_user;pwd=x");
b["server"] = "tcp:sql2.example.com,1433";
Console.WriteLine($"DataSource: {b.DataSource}");
Console.WriteLine($"ContainsKey(\"uid\"): {b.ContainsKey("uid")}");
Console.WriteLine($"Remove(\"pwd\"): {b.Remove("pwd")}");
Console.WriteLine($"TryGetValue: {b.TryGetValue("Database", out object? db)} {db}");
Console.WriteLine($"ShouldSerialize(\"Pooling\"): {b.ShouldSerialize("Pooling")}");
Console.WriteLine(b.ConnectionString);
try { b["Bogus"] = 1; } catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }
try { b.ConnectTimeout = -1; } catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }

輸出為:

DataSource: tcp:sql2.example.com,1433
ContainsKey("uid"): True
Remove("pwd"): True
TryGetValue: True Orders
ShouldSerialize("Pooling"): False
Data Source=tcp:sql2.example.com,1433;Initial Catalog=Orders;User ID=app_user
ArgumentException: Keyword not supported: 'Bogus'.
ArgumentException: Invalid value for key 'Connect Timeout'.

ShouldSerialize 對於不在字串中的關鍵字回傳 false 。 ContainsKey 對每個支援的關鍵字都回傳 true ,無論字串是否設定該關鍵字。 該 ConnectionString 屬性只包含你設定的關鍵字。

了解建置工具不會檢查的部分

建構者會拒絕未知關鍵字和許多無效值,但不會檢查每個值。 例如,它接受 Transaction Binding 和 Type System Version 的任何文字,且不檢查字串長度。 它也不會檢查關鍵字組合,因此會一併接受 Authentication=Active Directory Default 與 Integrated Security=true。 SqlConnection 當你傳遞字串給它時,會拒絕與 ArgumentException 的組合。 完整規則列表請參見 驗證規則。

建構者寫入Authentication不含空格的值,例如 Authentication=ActiveDirectoryDefault。 驅動程式接受兩種格式。

建構程式在密碼進入處理程序記憶體後,也不會保護該密碼,也不會決定伺服器、身分或憑證設定是否安全。

保留一個標準連線字串

每個不同的 連接字串 都有自己的連接池。 兩個手寫字串僅在關鍵字順序或同義詞上有所不同,會形成兩個池。 產生器以一致的順序寫入典範關鍵字,因此 Server=a;Database=d 和 Database=d;Server=a 兩者都會變成 Data Source=a;Initial Catalog=d。 啟動時建立一次 連接字串 並重複使用 builder.ConnectionString。 不要新增每個請求的值,例如使用者名稱或請求 ID。 更多連線池指引,請參見 使用連線池。