Сборщики строк соединений в 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 в качестве имени базы данных.

Экранирование предотвращает инъекцию ключевых слов. Он не определяет, разрешено ли значение. Проверяйте недоверенные данные по списку допустимых серверов или баз данных перед использованием.

Заполните базовую строку подключения

Сохраняйте части строка подключения, которые не меняются, такие как сервер и база данных, в конфигурации. Добавляйте секрет и специфические значения рабочей нагрузки во время выполнения. Передайте базовую строку конструктору построителя, а затем задайте свойства.

Эта программа считывает строку подключения и пароль из разных переменных среды, подключается с помощью 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 и Защита сведений о подключении.

Чтение и изменение значений

Билдер также работает как словарь. Индексатор, 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 включает только те ключевые слова, которые вы задали.

Поймите, что билдер не проверяет

Конструктор отклоняет неизвестные ключевые слова и множество недопустимых значений, но не проверяет каждое значение. Например, он принимает любой текст для Type System Version и Transaction Binding, и не проверяет длину строк. Он также не проверяет комбинации ключевых слов, поэтому принимает 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 её повторно. Не добавляйте значения для каждого запроса, такие как имена пользователей или идентификаторы запросов. Для получения дополнительных рекомендаций по пулированию см. раздел «Использование пула соединений».