使用 SqlBatch 通过一个已打开的 SqlConnection 发送有序的 SqlBatchCommand 对象集合。 每个批处理命令都有自己的命令文本、命令类型、参数、命令行为以及受影响行数结果。
当应用程序有需要一起发送的多个不同命令时,使用 SqlBatch。 当工作是一个语句或存储过程时,使用一个参数化的 SqlCommand。 对于大型行集,可以使用 SqlBulkCopy 或表值参数。
本文中的示例假定一个名为 connection 的已打开 SqlConnection 指向 AdventureWorks 示例数据库。
C# 示例要求 using Microsoft.Data.SqlClient;。 使用 SqlDbType 或 CommandBehavior 的示例也需要 using System.Data;。 使用 DbDataReader 该词的例子需要 using System.Data.Common;。
版本与平台支持
这些示例针对的是 Microsoft.Data.SqlClient 7.1.0。 为你的应用程序选择包版本和目标框架,两者都要在 Microsoft.Data.SqlClient 支持生命周期的支持范围内。
在现代 .NET 上,SqlBatch 派生自 DbBatch。 Microsoft.Data.SqlClient 还在其 .NET Framework 和 .NET Standard 2.0 包资产中提供 SqlBatch。 公共接口可能因目标框架而异,因此请为你要发布的每个目标编译应用程序。
创建批次
为一个开放连接创建一个批处理,然后按执行顺序添加命令:
using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand("""
SELECT TOP (3) ProductID, Name
FROM Production.Product
ORDER BY ProductID;
"""));
batch.BatchCommands.Add(new SqlBatchCommand("""
SELECT TOP (3) SalesOrderID, OrderDate
FROM Sales.SalesOrderHeader
ORDER BY SalesOrderID;
"""));
using DbDataReader reader = batch.ExecuteReader();
do
{
while (reader.Read())
{
Console.WriteLine($"{reader.GetValue(0)} | {reader.GetValue(1)}");
}
}
while (reader.NextResult());
CommandType.Text 是默认值。 将批处理命令设置为 CommandType.StoredProcedure,用于存储过程。 设置 CommandType.TableDirect 会引发 ArgumentOutOfRangeException。
为批处理命令添加参数
参数属于单个 SqlBatchCommand。 两个批处理命令中的相同占位符名称不共享一个值:
using var batch = new SqlBatch(connection);
var products = new SqlBatchCommand("""
SELECT ProductID, Name
FROM Production.Product
WHERE ProductSubcategoryID = @id
ORDER BY ProductID;
""");
products.Parameters.Add("@id", SqlDbType.Int).Value = 2;
batch.BatchCommands.Add(products);
var orders = new SqlBatchCommand("""
SELECT SalesOrderID, OrderDate
FROM Sales.SalesOrderHeader
WHERE SalesPersonID = @id
ORDER BY SalesOrderID;
""");
orders.Parameters.Add("@id", SqlDbType.Int).Value = 279;
batch.BatchCommands.Add(orders);
using DbDataReader reader = batch.ExecuteReader();
从数据库约定中设置每个参数的SQL类型以及适用的大小、精度和小数位数。 有关详细信息,请参阅配置参数。
执行数据更改命令
当批处理会更改数据且不需要结果行时,使用 ExecuteNonQuery 或 ExecuteNonQueryAsync。 返回的值是当行计数可用时受影响的行总数。 执行后,每个命令的 RecordsAffected 会报告其各自的计数。
以下示例回滚其更改:
using SqlTransaction transaction = connection.BeginTransaction();
using var batch = new SqlBatch(connection, transaction);
var firstUpdate = new SqlBatchCommand("""
UPDATE Sales.SalesOrderHeader
SET Status = @status
WHERE SalesOrderID = @salesOrderId;
""");
firstUpdate.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
firstUpdate.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;
batch.BatchCommands.Add(firstUpdate);
var secondUpdate = new SqlBatchCommand("""
UPDATE Sales.SalesOrderHeader
SET Status = @status
WHERE SalesOrderID = @salesOrderId;
""");
secondUpdate.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
secondUpdate.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43660;
batch.BatchCommands.Add(secondUpdate);
int totalRows = batch.ExecuteNonQuery();
Console.WriteLine($"Total rows: {totalRows}");
foreach (SqlBatchCommand command in batch.BatchCommands)
{
Console.WriteLine(command.RecordsAffected);
}
transaction.Rollback();
查看多个结果
当任何命令返回行时,使用 ExecuteReader 或 ExecuteReaderAsync。 读取器按批处理命令的顺序公开结果。 调用 NextResult 或 NextResultAsync,直到其返回 false。
命令可以产生零个行集、一个行集或多个行集。 不要假设一个 NextResult 调用对应一个 SqlBatchCommand。
使用命令行为
当读取器需要顺序访问或必须关闭连接时,将批处理级别 CommandBehavior 传递给 ExecuteReader 或 ExecuteReaderAsync:
using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand("""
SELECT DocumentSummary
FROM Production.Document
WHERE DocumentSummary IS NOT NULL;
"""));
using DbDataReader reader =
batch.ExecuteReader(CommandBehavior.SequentialAccess);
char[] buffer = new char[4096];
while (reader.Read())
{
using TextReader summary = reader.GetTextReader(0);
int count;
while ((count = summary.Read(buffer, 0, buffer.Length)) > 0)
{
Console.Out.Write(buffer, 0, count);
}
Console.WriteLine();
}
批次级重载仅接受 CommandBehavior.Default、CommandBehavior.SequentialAccess、CommandBehavior.CloseConnection 及这些值的组合。 其他有效值,如 SingleRow、SchemaOnly 和 KeyInfo,在传递给批处理级别重载时会抛出 ArgumentOutOfRangeException。 批处理级别行为重载从 Microsoft.Data.SqlClient 7.1 开始提供。
在单独命令上设置 CommandBehavior ,用于选项如 SingleRow、 SchemaOnly和 KeyInfo。 对 SequentialAccess 和 CloseConnection 使用批次级别的重载。
使用事务
SqlBatch 并没有使其命令成为原子操作。 当所有命令必须提交或回滚在一起时,使用显式事务。 此示例回滚,使AdventureWorks保持不变:
using SqlTransaction transaction = connection.BeginTransaction();
using var batch = new SqlBatch(connection, transaction);
batch.BatchCommands.Add(new SqlBatchCommand("""
UPDATE Sales.SalesOrderHeader
SET Status = 2
WHERE SalesOrderID = 43659;
"""));
batch.BatchCommands.Add(new SqlBatchCommand("""
UPDATE Sales.SalesOrderHeader
SET Status = 2
WHERE SalesOrderID = 43660;
"""));
batch.ExecuteNonQuery();
transaction.Rollback();
仅当应用程序接受完整批处理时,才调用 Commit 而不是 Rollback。 在执行前分配事务。 事务和批处理必须使用相同的连接。 SqlClient 未重写异步事务方法,因此 BeginTransactionAsync、CommitAsync 和 RollbackAsync 会同步完成。 更多信息请参见 使用基于任务的SQL事务方法。
处理超时、取消和错误
为批处理设置以秒为单位的 Timeout。 当你配置了一个连接时,批处理会使用该连接的 Command Timeout 设置。 否则,默认是30秒。 值 0 没有时间限制,负值则抛出 ArgumentException。 当调用者需要停止等待时,将取消令牌传递给异步执行方法,或调用 Cancel。 取消操作会尽力执行,但不保证成功。 操作可能会在取消生效前完成。
执行失败时,捕捉 SqlException 并检查其 Errors 集合中的每一项。
BatchCommand 在该信息可用时识别相关的 SqlBatchCommand:
using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand(
"SELECT TOP (1) ProductID FROM Production.Product;"));
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1 / 0;"));
try
{
using DbDataReader reader = batch.ExecuteReader();
do
{
while (reader.Read())
{
Console.WriteLine(reader.GetValue(0));
}
}
while (reader.NextResult());
}
catch (SqlException exception)
{
Console.WriteLine(
exception.BatchCommand is null
? "The batch failed."
: $"Failed command: {exception.BatchCommand.CommandText}");
}
读取器执行是增量式的。 后续批处理命令的错误可能会在 Read 或 NextResult 前进到该命令时出现,因此应在 try 块内处理读取器。
不要记录包含凭证、令牌或敏感数据的参数值。 关于错误字段和信息消息,请参见 处理错误和消息。