Выполнение команды

Используйте SqlCommand для выполнения инструкций Transact-SQL (T-SQL) или хранимых процедур. Команда нуждается в открытом SqlConnection. Освободите команду после использования и освободите любой объект чтения, прежде чем использовать соединение повторно.

В примерах этой статьи предполагается, что открытый SqlConnection с именем connection нацелен на образцовую базу данных AdventureWorks.

Примеры C# требуют using Microsoft.Data.SqlClient;. Примеры, использующие SqlDbType, CommandType или CommandBehavior, также требуют using System.Data;.

Для настройки соединения, аутентификации и шифрования см. Строки соединения и Шифрование и проверка сертификатов.

Выберите метод выполнения

Выберите метод, который соответствует ожидаемому результату:

Метод Используйте, если Результат
ExecuteNonQuery Команда меняет данные или схему. Количество затронутых строк. -1 может возникать при отсутствии подсчёта, в том числе когда SET NOCOUNT ON подавляет подсчёт.
ExecuteScalar Вам нужно одно значение. Первый столбец первой строки, или null когда в результате нет строк. База данных NULL возвращается как Value.
ExecuteReader Нужны строки, несколько наборов результатов или схема. А SqlDataReader.
ExecuteXmlReader Команда возвращает XML в форме, необходимой для ExecuteXmlReader. Объект XmlReader.

Каждый метод имеет асинхронный аналог, основанный на задачах. Передайте CancellationToken, когда вызывающему коду нужно перестать ждать. Сведения о поведении при отмене и тайм-ауте см. в разделе асинхронное программирование.

Выполнить текстовую команду

CommandType.Text — значение по умолчанию. Вставьте оператор в CommandText и передайте значения через параметры:

const string sql = """
    SELECT ProductID, Name, ListPrice
    FROM Production.Product
    WHERE ProductSubcategoryID = @subcategoryId
    ORDER BY ListPrice DESC;
    """;

using var command = new SqlCommand(sql, connection);
command.Parameters.Add("@subcategoryId", SqlDbType.Int).Value = 2;

using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(
        $"{reader.GetInt32(0)} | {reader.GetString(1)} | {reader.GetDecimal(2):C}");
}

Сведения о типах параметров, размерах, нулевых значениях и направлениях см. в разделе Настройка параметров. Не конкатенируйте недоверенные значения с CommandText.

Выполнение хранимой процедуры

Установите CommandType.StoredProcedure и используйте имя процедуры, квалифицированное по схеме:

using var command = new SqlCommand(
    "dbo.uspGetEmployeeManagers",
    connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(
    "@BusinessEntityID",
    SqlDbType.Int).Value = 6;

using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(
        $"{reader.GetString(2)} {reader.GetString(3)}");
}

Microsoft. Data.SqlClient поддерживает CommandType.Text и CommandType.StoredProcedure. Параметр CommandType.TableDirect вызывает ArgumentOutOfRangeException.

Проверьте, что даёт каждый метод

Используйте ExecuteScalar тогда, когда нужно одно значение:

using var command = new SqlCommand(
    "SELECT COUNT(*) FROM Production.Product WHERE SellEndDate IS NULL;",
    connection);

int currentProductCount = (int)command.ExecuteScalar()!;
Console.WriteLine($"Current products: {currentProductCount}");

ExecuteScalar возвращает null, когда в результате нет строк, и DBNull.Value, когда первое значение — значение базы данных NULL.

Используйте ExecuteNonQuery для операторов, изменяющих данные. Этот пример откатывает изменения, так что AdventureWorks остаётся без изменений:

using SqlTransaction transaction = connection.BeginTransaction();

using var command = new SqlCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """, connection, transaction);
command.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
command.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;

int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"Rows affected: {rowsAffected}");

transaction.Rollback();

SET NOCOUNT ON подавляет количество затронутых строк, поэтому ExecuteNonQuery может возвращать -1 даже когда оператор изменяет данные.

Используйте ExecuteXmlReader для получения результата FOR XML:

using var command = new SqlCommand("""
    SELECT TOP (3) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID
    FOR XML PATH('Product'), ROOT('Products');
    """, connection);

using System.Xml.XmlReader reader = command.ExecuteXmlReader();
reader.MoveToContent();
Console.WriteLine(reader.ReadOuterXml());

Используйте локальную транзакцию

Когда соединение имеет активное локальное SqlTransaction, назначайте эту транзакцию каждой команде, выполняемой на соединении. Этот пример откатывает изменения, так что AdventureWorks остаётся без изменений:

using SqlTransaction transaction = connection.BeginTransaction();

using var command = new SqlCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """, connection, transaction);
command.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
command.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;

command.ExecuteNonQuery();
transaction.Rollback();

Вызывайте Commit вместо Rollback только тогда, когда приложение принимает всю транзакцию. Команда, не ссылающаяся на активную транзакцию, вызывает InvalidOperationException. Выполнение также завершается с ошибкой, если транзакция принадлежит другому соединению. Для границ транзакций и отката см. раздел «Локальные транзакции».

Задайте тайм-аут команды

CommandTimeout ограничивает выполнение команд и накопленное ожидание чтения из сети в секундах. Команда использует параметр подключения Command Timeout, если он задан. В противном случае по умолчанию — 30 секунд. Значение 0 не имеет ограничения по времени. При отрицательных значениях возникает ArgumentException.

Когда тайм-аут истекает, команда вызывает исключение SqlException с параметром Number, установленным в значение -2:

using var command = new SqlCommand(
    "WAITFOR DELAY '00:00:05';",
    connection)
{
    CommandTimeout = 1
};

try
{
    command.ExecuteNonQuery();
}
catch (SqlException exception) when (exception.Number == -2)
{
    Console.WriteLine("The command timed out.");
}

Connect Timeout Ограничение установления соединений и ожидания в пуле соединений. CommandTimeout начинается после начала выполнения команды. Для получения дополнительной информации смотрите раздел «Параметры подключения».

Используйте ограниченный тайм-аут и токен отмены вместе. Тайм-аут устанавливает лимит на операцию с базой данных. Отмена токена может вернуть контроль вызывающему до истечения тайм-аута. Не думайте, что отмена и тайм-аут приводят к одному и тому же исключению.

Повторное использование и подготовка команд

Добавьте параметр один раз, а затем изменяйте его значение после завершения каждого выполнения:

using var command = new SqlCommand("""
    SELECT TOP (1) Name, ListPrice
    FROM Production.Product
    WHERE ProductSubcategoryID = @subcategoryId
    ORDER BY ListPrice DESC;
    """, connection);
SqlParameter subcategoryId = command.Parameters.Add(
    "@subcategoryId",
    SqlDbType.Int);

foreach (int id in new[] { 1, 2, 3 })
{
    subcategoryId.Value = id;
    using SqlDataReader reader = command.ExecuteReader();
    if (reader.Read())
    {
        Console.WriteLine($"{id}: {reader.GetString(0)}");
    }
}

Звоните Prepare только тогда, когда измерения это оправдывают. Prepare не готовит хранимые процедуры или текстовые команды без параметров. Для параметризованного текста установите полные метаданные, такие как Size, Scale и Prepare, для каждого параметра перед вызовом Precision.

Команды устранения неполадок

Перехватите SqlException при выполнении операции с базой данных и проверьте её коллекцию Errors, если сервер возвращает более одной ошибки. Для ошибок команд, предупреждений и информационных сообщений см. раздел «Обработка ошибок и сообщений».

Используйте диагностические счётчики, отслеживание источников событий и руководство по устранению неполадок SqlClient , когда ошибка требует дополнительного контекста. Применяйте настраиваемую логику повтора только тогда, когда повторение команды безопасно.