Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
SqlConnectionпредставляет собой одно логическое соединение с SQL Server, База данных SQL Azure, Управляемый экземпляр SQL Azure или базой данных SQL в Microsoft Fabric. При открытии объекта ему выделяется физическое соединение из пула соединений, если оно доступно. Закрытие или утилизация возвращает физическое соединение с пулом.
Используйте объекты SqlConnection с коротким сроком жизни для отдельных единиц работы. Не оставляйте открытым одно глобальное соединение для приложения.
Prerequisites
- .NET SDK 10 или более поздний поддерживаемый .NET SDK. SDK работает на Windows, Linux и macOS. Также создаётся версия .NET Framework для каждого сэмпла, которая работает только в Windows и требует .NET Framework 4.8.1.
- База данных, к которой можно подключиться в SQL Server, База данных SQL Azure, Управляемый экземпляр SQL Azure или SQL Database в Microsoft Fabric.
Создание проекта
Microsoft.Data.SqlClient 7.0 и более поздние версии требуют пакета Microsoft.Data.SqlClient.Extensions.Azure, соответствующего версии, для режимов аутентификации Microsoft Entra ID, предоставляемых драйвером, таких как Active Directory Default. Если ваше приложение использует только SQL-аутентификацию или интегрированную аутентификацию Windows, вы можете её исключить.
В пустой папке выполните следующие команды:
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
Примеры в этой статье читают строка подключения из SQL_CONNECTION_STRING переменной среды. Установите его перед тем, как запускать сэмпл. О форматах строк подключения см. Connection strings.
Каждый образец, кроме примера транзакции, является полноценной программой. Чтобы запустить один из них, замените содержимое Program.cs на него, а затем запустите dotnet run. Пример транзакции — это фрагмент для вашего метода. Вкладка .NET использует операторы верхнего уровня, неявные using директивы и await using. Вкладка .NET Framework использует C# 7.3, который не поддерживает await using, поэтому использует операторы using вместо этого.
Откройте соединение
Вызывайте OpenAsync в асинхронном коде или Open в синхронном коде. Объявите соединение с await using или using таким образом, чтобы оно освобождалось при выходе из области видимости, даже если создаётся исключение.
При интегрированной аутентификации Windows соединение использует учетные данные процесса. Для веб-приложения или сервиса Windows это пул приложений или сервисная учетная запись, а не авторизованный пользователь. Дополнительные сведения см. в «Выбрать паттерн аутентификации».
Эта полная программа открывает соединение, сообщает о каждом изменении состояния и читает два свойства сервера:
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)}");
}
Для версии SQL Server 2025 Enterprise результат выглядит следующим образом:
State: Closed to Open
Edition: Enterprise Edition: Core-based Licensing (64-bit)
EngineEdition: 3
State: Open to Closed
Последняя строка появляется, когда соединение освобождается в конце программы.
Закрыть и освободить соединения
Всегда закрывайте соединение после завершения, чтобы физическое соединение вернулось в пул. Операторы await using и using закрывают соединение при выходе из блока, включая случаи, когда выбрасывается необработанное исключение. Вы также можете позвонить Close или Dispose напрямую.
Close и Dispose оказывают такое же влияние на физическое соединение.
Следуйте этим правилам:
- Избавляйтесь от считывателей и команд до соединения, которое ими владеет.
- Не полагайтесь на сборку мусора для возврата соединения в пул. Соединение, выходящее из области видимости без закрытия, не возвращается своевременно, и в пуле могут закончиться соединения.
- Не вызывайте
CloseилиDisposeдля соединения, средства чтения или любого другого управляемого объекта в финализаторе класса. Финалайзер должен освобождать только неуправляемые ресурсы, которыми класс напрямую владеет. Для получения дополнительной информации см. раздел «Сборка мусора». - После
Closeвы можете снова вызватьOpenна том же объекте. ПослеDispose, создайте новый объект.
При включённом пуллинге закрытие соединения возвращает его в пул без закрытия сетевого соединения. Пул сбрасывает состояние сессии при повторном использовании соединения. Для получения дополнительной информации см. SQL Server connection pooling.
Использование асинхронных API
Используйте асинхронные вызовы для работы с сетевыми базами данных в веб-серверах, сервисах, пользовательских интерфейсах и рабочих:
OpenAsync(cancellationToken)ExecuteNonQueryAsync(cancellationToken)ExecuteReaderAsync(cancellationToken)ExecuteScalarAsync(cancellationToken)ReadAsync(cancellationToken)
Не начинайте новую операцию на соединении, команде или считывателе до завершения текущей асинхронной операции.
Применение отмены и тайм-аута
Передайте звонящим CancellationToken через каждый асинхронный вызов базы данных. Отмена требует от водителя прервать ожидающую задачу, но немедленное завершение не гарантировано. Продолжайте использовать ограниченное соединение и тайм-ауты команд.
Эти органы управления имеют отдельные задачи:
| Управление | Объем |
|---|---|
Connect Timeout |
Установление соединения или ожидание соединения из пула |
SqlCommand.CommandTimeout |
Одно выполнение команды |
CancellationToken |
Отмена асинхронной операции по запросу звонящего |
Тайм-аут или отмена не доказывают, что сервер откатил операцию назад. Используйте транзакцию, когда несколько изменений должны быть зафиксированы или отменены как одно целое, и принимайте решение о повторной попытке на основе идемпотентности операции и результата транзакции.
Отслеживать состояние соединения
Свойство State возвращает снимок перечисления ConnectionState.
| State | Значение |
|---|---|
Closed |
Логическая связь не открыта. |
Connecting |
Открытая операция идёт. |
Open |
Логическая связь открыта. |
Событие StateChange запускается, когда меняется состояние. Его StateChangeEventArgs аргумент обладает двумя свойствами:
-
OriginalState— это состояние до изменения. -
CurrentStateэто состояние после изменения.
Драйвер сообщает о переходах от Closed к Open и от Open к Closed, как видно в примере в Open a connection. Не рассматривайте Executing, Fetching или Broken как фазы жизненного цикла приложения.
Не используйте State для проверки работоспособности перед каждой командой. Сеть может выходить из строя после любой проверки. Выполните операцию и обработайте возникшее исключение. Используйте StateChange для диагностики, а не для координации параллельной работы.
Получайте информационные сообщения
Событие InfoMessage доставляет сообщения, которые сервер отправляет, не вызывая ошибки. К таким сообщениям относятся PRINT выходные данные, RAISERROR сообщения с уровнем серьезности 10 и ниже, а также предупреждения сервера. Ошибки с серьёзностью 11 и выше вызывают SqlException вместо этого. Для уровней серьёзности см. Уровни серьезности ошибок ядро СУБД.
Обработчик событий получает объект SqlInfoMessageEventArgs. Свойство Errors содержит коллекцию объектов SqlError. В каждом из них указаны номер сообщения, текст, класс тяжести, процедура и номер строки.
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();
Результат выглядит так:
Message 0, severity 0: Starting the batch.
Message 50000, severity 0: Processed 42 rows.
Сервер сообщает RAISERROR о степени серьёзности 10 как класс 0, то есть SqlError.Class показывает 0 для обоих сообщений.
Обрабатывайте ошибки пользователя как информационные сообщения
По умолчанию драйвер сообщает об ошибке со степенью тяжести 11 или выше, выбрасывая SqlException. Это исключение не означает, что сервер остановил пакетную обработку. Многие ошибки, включая RAISERROR с уровнем серьёзности 16, позволяют серверу выполнять оставшиеся операторы, поэтому последующие операторы могут изменять данные, даже когда ваш код перехватывает исключение. Чтобы получить эти ошибки в виде сообщений вместо исключений, установите FireInfoMessageEventOnUserErrors на true. Затем драйвер отправляет ошибки с уровнем серьёзности 16 или ниже в событие InfoMessage, а не вызывает исключение. Это свойство меняет способ сообщения драйвера об ошибках. Это не меняет, какие инструкции выполняет сервер.
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());
}
Результат выглядит так:
Message 50000, severity 16: Row 7 failed validation.
The batch continued.
Ошибки с уровнем серьёзности 17 и выше всё равно генерируют SqlException, независимо от этой настройки. Когда вы включаете это свойство, ваш обработчик отвечает за обнаружение сбоев. Код, ожидающий исключения для каждой ошибки, не видит исключения.
Сообщить об идентификаторе приложения
Начиная с Microsoft. Data.SqlClient 7.1.0, фреймворк, инструмент или другое промежуточное ПО, может сообщать, какое приложение открыло соединение. Установите для RegisteredApplication значение RegisteredApplication до открытия соединения. Драйвер отправляет значение серверу один раз во время входа.
Следуйте этим правилам:
- Установите значение через свойство. Для этого не существует ключевого слова строки подключения.
- Установите значение до открытия соединения. Установка во время открытия соединения или когда оно уже открыто вызывает InvalidOperationException.
- Воспринимайте значение как телеметрию. Клиент предоставляет значение и может сообщать любой идентификатор, поэтому сервер не проверяет его подлинность.
- Ожидайте, что соединения из пула будут указывать исходные учетные данные. Значение не входит в ключ пула. Соединение, полученное из пула, сообщает об идентификаторе логического соединения, которое вызвало создание физического соединения.
Caution
Никогда не используйте RegisteredApplication для предоставления прав или любых других решений, связанных с безопасностью. Любой клиент может сообщить о любом идентификаторе.
Выберите идентификатор
Значение по умолчанию — Unknown, то есть 0. Перечисление имеет базовый тип ushort, поэтому идентификаторы варьируются от 0 до 0xFFFF.
| Диапазон | Использовать |
|---|---|
0x0001–0x7FFF |
Приложения, определённые Microsoft. Именованные члены перечисления находятся в этом диапазоне. |
0x8000–0xBFFF |
Зарезервировано для мелкомасштабного использования. |
0xC000–0xFFFF |
Общедоступное использование и использование разработчиками. |
Названные члены — EntityFrameworkCore, SemanticKernel, ManagementStudio, SqlManagementObjects, DataTierApplicationFramework, SqlToolsService, AspNetCoreDistributedSqlServerCache, OrleansAdoNet, DataApiBuilder, EntityFramework, AzureFunctionsSqlExtension, DurableTaskSqlServer и SqlPackage. Чтобы сообщить об идентификаторе, у которого нет именованного члена, приведите число к типу RegisteredApplication.
Тип RegisteredApplication и свойство RegisteredApplication не совместимы с CLS, потому что в перечислении используется ushort. Код на C# может использовать их напрямую.
Установите идентификатор
Эта программа сообщает идентификатор из диапазона разработчика и показывает, что происходит, когда код меняет значение после открытия соединения:
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}");
}
Результат выглядит так:
Reported identifier: 0xC001
InvalidOperationException: Not allowed to change the 'RegisteredApplication' property. The connection's current state is open.
Понять влияние пулирования
Драйвер сообщает об идентификаторе только при создании нового физического соединения. При включенном пуле подключений:
- Соединение, повторно использующее физическое соединение из пула, сообщает идентификатор соединения, создавшего физическое соединение, а не собственный.
- Физические соединения, которые пул открывает в фоновом режиме для формирования
Min Pool SizeотчетаUnknown.
Если одна строка подключения служит нескольким учетным записям, рассматривайте указанное значение как приблизительное. Отключите пул для этой строки подключения, когда нужна точная атрибуция, и сначала измерьте дополнительную стоимость соединения.
SqlConnection.Clone копирует значение RegisteredApplication в новое подключение.
Не делитесь соединением одновременно
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);
Если операция завершается с ошибкой до коммита, освобождение объекта транзакции приводит к её откату. Держите транзакции короткими. Не выполняйте сетевые звонки, не взаимодействуйте с пользователем или не связанные вычисления, пока транзакция базы данных заблокирована.
Когда System.Transactions.Transaction.Current активен, OpenAsync и Open автоматически подключаются по умолчанию. Устанавливать Enlist=false только тогда, когда операция должна оставаться вне окружающей транзакции.
Измерьте одну логическую связь
Установите для StatisticsEnabled значение true, чтобы собирать статистику драйверов для одного объекта SqlConnection:
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}");
Вывод показывает один цикл обмена. Количество строк зависит от количества баз данных на сервере:
ServerRoundtrips: 1
SelectRows: 14
BytesReceived > 0: True
RetrieveStatistics возвращает снимок.
ResetStatistics обозначает начало новой области измерения. Установите StatisticsEnabled в значение false, чтобы прекратить сбор. Собранные на данный момент значения остаются доступными. Статистика ведётся по объекту соединения и добавляет накладные расходы, поэтому включите их для целенаправленной диагностики, а не для каждого производственного запроса.
Обработка неудачных подключений
Перехватывайте SqlException на уровне границы, которая может зарегистрировать, преобразовать сбой или повторить попытку. Записи:
NumberStateClassClientConnectionId- Имя операции и настроенные идентификаторы сервера и базы данных
Не записывайте строка подключения, пароль, секрет клиента или токен доступа.
Избавьтесь от сломанного соединения. Пул удаляет недействительные физические соединения при их обнаружении. Если изменились учетные данные, токен, сертификат, цель DNS или сервер, исправьте конфигурацию перед повторной попыткой.
Используйте ограниченную логику повторных попыток только для временных сбоев. Повторная попытка при первом открытии, восстановление в режиме простоя соединения и повторная попытка команды — это разные механизмы. Для получения дополнительной информации см. логику повторных попыток и параметры восстановления соединения в разделе Параметры подключения.