Führe mehrere Befehle mit SqlBatch aus

Verwenden Sie SqlBatch, um eine geordnete Sammlung von SqlBatchCommand Objekten durch ein offenes SqlConnection zu schicken. Jeder Batchbefehl hat seinen eigenen Befehlstext, Befehlstyp, Parameter, Befehlsverhalten und ein eigenes Ergebnis der betroffenen Zeilen.

Verwenden Sie SqlBatch, wenn eine Anwendung unterschiedliche Befehle zusammen senden möchte. Verwenden Sie einen parametrisierten SqlCommand, wenn der Vorgang eine Anweisung oder eine gespeicherte Prozedur ist. Verwenden Sie SqlBulkCopy oder einen tabellenwertigen Parameter für große Zeilensätze.

In den Beispielen in diesem Artikel wird vorausgesetzt, dass eine geöffnete SqlConnection mit dem Namen connection auf die AdventureWorks-Beispieldatenbank abzielt.

Die C#-Beispiele erfordern using Microsoft.Data.SqlClient;. Beispiele, die SqlDbType oder CommandBehavior verwenden, erfordern auch using System.Data;. Beispiele, die DbDataReader verwenden, erfordern using System.Data.Common;.

Unterstützung für Versionen und Plattformen

Die Beispiele beziehen sich auf Microsoft.Data.SqlClient 7.1.0. Wählen Sie eine Paketversion und ein Zielframework aus, die der Supportlebenszyklus von Microsoft.Data.SqlClient für Ihre Anwendung unterstützt.

SqlBatch leitet sich unter modernem .NET von DbBatch ab. Microsoft. Data.SqlClient stellt SqlBatch außerdem in seinem .NET Framework und .NET Standard 2.0 Paket-Assets bereit. Die öffentliche API-Oberfläche kann je nach Zielframework unterschiedlich sein, daher kompiliere die Anwendung für jedes Ziel, das du auslieferst.

Batch erstellen

Erstelle einen Batch für eine offene Verbindung und füge dann Befehle in der Ausführungsreihenfolge hinzu:

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 ist der Standardwert. Legen Sie einen Batchbefehl für eine gespeicherte Prozedur auf CommandType.StoredProcedure fest. Das Setzen von CommandType.TableDirect löst ArgumentOutOfRangeException aus.

Fügen Sie Parameter zu Batch-Befehlen hinzu

Parameter gehören zu einem einzelnen SqlBatchCommand. Derselbe Platzhaltername in zwei Batchbefehlen haben keinen gemeinsamen Wert:

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();

Legen Sie den SQL-Typ jedes Parameters sowie die anwendbare Größe, Präzision und Skalierung aus dem Datenbankvertrag fest. Weitere Informationen finden Sie unter Konfigurieren von Parametern.

Datenändernde Befehle ausführen

Verwenden Sie ExecuteNonQuery oder ExecuteNonQueryAsync, wenn der Batch die Daten ändert und Sie keine Ergebniszeilen benötigen. Der zurückgegebene Wert ist die Gesamtzahl der betroffenen Zeilen, wenn Zeilenzählungen verfügbar sind. Nach der Ausführung meldet jeder RecordsAffected des Befehls seine eigene Anzahl.

Dieses Beispiel macht seine Änderungen rückgängig:

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();

Mehrere Ergebnisse lesen

Verwenden Sie ExecuteReader oder ExecuteReaderAsync, wenn ein Befehl Zeilen zurückgibt. Der Leser gibt die Ergebnisse in der Reihenfolge der Batch-Befehle aus. Rufen Sie NextResult oder NextResultAsync auf, bis false zurückgegeben wird.

Ein Befehl kann keine Zeilenmenge, eine Zeilenmenge oder mehrere Zeilenmengen erzeugen. Gehen Sie nicht davon aus, dass ein NextResult-Aufruf einem SqlBatchCommand entspricht.

Befehlsverhalten verwenden

Übergeben Sie ein CommandBehavior auf Batch-Ebene an ExecuteReader oder ExecuteReaderAsync, wenn der Leser sequenziellen Zugriff benötigt oder die Verbindung schließen muss:

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();
}

Die Batch-Überladungen akzeptieren nur CommandBehavior.Default, CommandBehavior.SequentialAccess, CommandBehavior.CloseConnection und Kombinationen dieser Werte. Andere gültige Werte wie SingleRow, KeyInfo und ArgumentOutOfRangeException lösen SchemaOnly aus, wenn sie an eine Überladung auf Batchebene übergeben werden. Die Verhaltensüberladungen auf Batchebene sind ab Microsoft.Data.SqlClient 7.1 verfügbar.

Legen Sie CommandBehavior für einen einzelnen Befehl für Optionen wie SingleRow, SchemaOnly und KeyInfo fest. Verwenden Sie den Batch-Level-Overload für SequentialAccess und CloseConnection.

Eine Transaktion verwenden

SqlBatch führt seine Befehle nicht atomar aus. Verwenden Sie eine explizite Transaktion, bei der alle Befehle gemeinsam committen oder zurückrollen müssen. Dieses Beispiel rollt zurück, sodass AdventureWorks unverändert bleibt:

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();

Rufen Sie Commit anstelle von Rollback nur auf, wenn die Anwendung den vollständigen Batch akzeptiert. Weise die Transaktion vor der Ausführung zu. Die Transaktion und der Batch müssen dieselbe Verbindung verwenden. SqlClient überschreibt die asynchronen Transaktionsmethoden nicht, also werden BeginTransactionAsync, CommitAsync und RollbackAsync synchron abgeschlossen. Weitere Informationen finden Sie unter Aufgabenbasierte SQL-Transaktionsmethoden verwenden.

Zeitüberschreitungen, Abbrüche und Fehler handhaben

Timeout für die Charge in Sekunden einstellen. Der Batch verwendet die Command Timeout Einstellung der Verbindung, wenn du eine konfigurierst. Ansonsten sind standardmäßig 30 Sekunden eingestellt. Ein Wert von 0 hat keine Zeitbegrenzung, und negative Werte werfen ArgumentException. Übergeben Sie ein Abbruchtoken an eine asynchrone Ausführungsmethode, wenn der Aufrufer das Warten beenden muss, oder rufen Sie Cancel auf. Der Abbruch erfolgt nach dem Best-Effort-Prinzip. Die Operation könnte abgeschlossen werden, bevor der Abbruch wirksam wird.

Wenn die Ausführung fehlschlägt, fangen Sie SqlException ab und inspizieren Sie jedes Element in seiner Errors Sammlung. BatchCommand identifiziert das zugehörige SqlBatchCommand, wenn diese Information verfügbar ist:

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}");
}

Die Ausführung des Lesers erfolgt inkrementell. Ein Fehler aus einem späteren Batchbefehl kann auftreten, wenn Read oder NextResult zu diesem Befehl gelangt, verarbeiten Sie den Leser daher im try-Block.

Protokolliere keine Parameterwerte, die Zugangsdaten, Token oder sensible Daten enthalten. Für Fehlerfelder und Informationsnachrichten lesen Sie Mit Fehlern und Meldungen umgehen.