diff --git a/doc/samples/SqlBatch_ExecuteReader.cs b/doc/samples/SqlBatch_ExecuteReader.cs
new file mode 100644
index 0000000000..aadfbd3508
--- /dev/null
+++ b/doc/samples/SqlBatch_ExecuteReader.cs
@@ -0,0 +1,46 @@
+//
+using Microsoft.Data.SqlClient;
+
+class Program
+{
+ static void Main()
+ {
+ string str = "Data Source=(local);Initial Catalog=Northwind;"
+ + "Integrated Security=SSPI;Encrypt=False";
+ RunBatch(str);
+ }
+
+ static void RunBatch(string connString)
+ {
+ using var connection = new SqlConnection(connString);
+ connection.Open();
+
+ var batch = new SqlBatch(connection);
+
+ const int count = 10;
+ const string parameterName = "parameter";
+ for (int i = 0; i < count; i++)
+ {
+ var batchCommand = new SqlBatchCommand($"SELECT @{parameterName} as value");
+ batchCommand.Parameters.Add(new SqlParameter(parameterName, i));
+ batch.BatchCommands.Add(batchCommand);
+ }
+
+ // Optionally Prepare
+ batch.Prepare();
+
+ var results = new List(count);
+ using (SqlDataReader reader = batch.ExecuteReader())
+ {
+ do
+ {
+ while (reader.Read())
+ {
+ results.Add(reader.GetFieldValue(0));
+ }
+ } while (reader.NextResult());
+ }
+ Console.WriteLine(string.Join(", ", results));
+ }
+}
+//
diff --git a/doc/snippets/Microsoft.Data.Common/DbBatch.xml b/doc/snippets/Microsoft.Data.Common/DbBatch.xml
new file mode 100644
index 0000000000..1c9dcdd68b
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.Common/DbBatch.xml
@@ -0,0 +1,651 @@
+
+
+
+
+
+
+
+
+ System.Object
+
+
+
+ System.IAsyncDisposable
+
+
+ System.IDisposable
+
+
+
+
+ [System.Runtime.CompilerServices.Nullable(0)]
+ [<System.Runtime.CompilerServices.Nullable(0)>]
+
+
+ [System.Runtime.CompilerServices.NullableContext(1)]
+ [<System.Runtime.CompilerServices.NullableContext(1)>]
+
+
+
+ Represents a batch of commands which can be executed against a data source in a single round trip. Provides a base class for database-specific classes that represent command batches.
+
+
+
+
+
+
+
+
+
+
+ Constructor
+
+
+
+ Initializes a new instance of the class.
+
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Data.Common.DbBatchCommandCollection
+
+
+
+ Gets the collection of objects.
+
+ The commands contained within the batch.
+ To be added.
+
+
+
+
+
+ Method
+
+ System.Void
+
+
+
+
+ Attempts to cancel the execution of a .
+
+
+
+
+
+
+
+
+
+
+
+ Property
+
+
+ [System.Runtime.CompilerServices.Nullable(2)]
+ [<System.Runtime.CompilerServices.Nullable(2)>]
+
+
+ [get: System.Runtime.CompilerServices.NullableContext(2)]
+ [<get: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+ [set: System.Runtime.CompilerServices.NullableContext(2)]
+ [<set: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+
+ System.Data.Common.DbConnection
+
+
+
+ Gets or sets the used by this .
+
+ The connection to the data source.
+ To be added.
+
+
+
+
+
+ Method
+
+ System.Data.Common.DbBatchCommand
+
+
+
+
+ Creates a new instance of a object.
+
+
+ A object.
+
+ To be added.
+
+
+
+
+
+ Method
+
+ System.Data.Common.DbBatchCommand
+
+
+
+
+ When overridden in a derived class, creates a new instance of a object.
+
+
+ A object.
+
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Data.Common.DbBatchCommandCollection
+
+
+
+ When overridden in a derived class, gets the collection of objects.
+
+ The commands contained within the batch.
+ To be added.
+
+
+
+
+
+ Property
+
+
+ [System.Runtime.CompilerServices.Nullable(2)]
+ [<System.Runtime.CompilerServices.Nullable(2)>]
+
+
+ [get: System.Runtime.CompilerServices.NullableContext(2)]
+ [<get: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+ [set: System.Runtime.CompilerServices.NullableContext(2)]
+ [<set: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+
+ System.Data.Common.DbConnection
+
+
+
+ When overridden in a derived class, gets or sets the used by this .
+
+ The connection to the data source.
+ To be added.
+
+
+
+
+
+ Property
+
+
+ [System.Runtime.CompilerServices.Nullable(2)]
+ [<System.Runtime.CompilerServices.Nullable(2)>]
+
+
+ [get: System.Runtime.CompilerServices.NullableContext(2)]
+ [<get: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+ [set: System.Runtime.CompilerServices.NullableContext(2)]
+ [<set: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+
+ System.Data.Common.DbTransaction
+
+
+
+ When overridden in a derived class, gets or sets the within which this object executes.
+
+
+ The transaction within which a batch of a .NET data provider executes. The default value is a null reference ( in Visual Basic).
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.IDisposable.Dispose
+
+
+ System.Void
+
+
+
+ Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.IAsyncDisposable.DisposeAsync
+
+
+ System.Threading.Tasks.ValueTask
+
+
+
+ Asynchronously diposes the batch object.
+
+ A representing the asynchronous operation.
+
+
+
+ , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+]]>
+
+
+
+
+
+
+
+ Method
+
+ System.Data.Common.DbDataReader
+
+
+
+
+
+
+ An instance of , specifying options for batch execution and data retrieval.
+
+
+ When overridden in a derived class, executes the batch against its connection, returning a which can be used to access the results.
+
+
+ A object.
+
+
+
+ can be used to advance the reader to the next result set.
+
+ ]]>
+
+
+ An error occurred while executing the batch.
+
+ The value is invalid.
+
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task<System.Data.Common.DbDataReader>
+
+
+
+
+
+
+ One of the enumeration values that specifies options for batch execution and data retrieval.
+ A token to cancel the asynchronous operation.
+
+ Providers should implement this method to provide a non-default implementation for overloads.
+
+ The default implementation invokes the synchronous method and returns a completed task, blocking the calling thread. The default implementation will return a cancelled task if passed an already cancelled cancellation token. Exceptions thrown by ExecuteReader will be communicated via the returned Task Exception property.
+
+ This method accepts a cancellation token that can be used to request the operation to be cancelled early. Implementations may ignore this request.
+
+ A task representing the asynchronous operation.
+
+
+ , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+]]>
+
+
+
+
+
+
+
+ Method
+
+ System.Int32
+
+
+
+ Executes the batch against its connection object, returning the total number of rows affected across all the batch commands.
+ The total number of rows affected across all the batch commands.
+
+
+ to perform catalog operations (for example, querying the structure of a database or creating database objects such as tables), or to change the data in a database by executing UPDATE, INSERT, or DELETE statements.
+
+ Although does not return any rows, any output parameters or return values mapped to parameters are populated with data.
+
+ For UPDATE, INSERT, and DELETE statements, the return value is the total number of rows affected by the batch. If no UPDATE, INSERT, or DELETE statements are included in the batch, the return value is -1.
+
+ ]]>
+
+
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task<System.Int32>
+
+
+
+
+
+ A token to cancel the asynchronous operation.
+
+ This is the asynchronous version of . Providers should override with an appropriate implementation. The cancellation token may optionally be ignored.
+
+ The default implementation invokes the synchronous method and returns a completed task, blocking the calling thread. The default implementation will return a cancelled task if passed an already cancelled cancellation token. Exceptions thrown by will be communicated via the returned Task Exception property.
+
+ Do not invoke other methods and properties of the object until the returned Task is complete.
+
+ A task representing the asynchronous operation.
+
+
+ , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+]]>
+
+
+ An error occurred while executing the batch.
+ ADO.NET Overview
+
+
+
+
+
+ Method
+
+ System.Data.Common.DbDataReader
+
+
+
+
+
+ One of the enumeration values that specifies options for batch execution and data retrieval.
+
+ Executes the batch against its connection, returning a which can be used to access the results.
+
+
+ A object.
+
+
+
+ can be used to advance the reader to the next result set.
+
+ ]]>
+
+
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task<System.Data.Common.DbDataReader>
+
+
+
+
+
+ To be added.
+
+ An asynchronous version of , which executes the batch against its connection, returning a which can be used to access the results.
+
+ A task representing the asynchronous operation.
+
+
+ in . For more information about asynchronous programming, see [Asynchronous Programming](/dotnet/framework/data/adonet/asynchronous-programming).
+
+ ]]>
+
+
+ An error occurred while executing the batch.
+
+ The value is invalid.
+
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task<System.Data.Common.DbDataReader>
+
+
+
+
+
+
+ One of the enumeration values that specifies options for batch execution and data retrieval.
+ A token to cancel the asynchronous operation.
+
+ An asynchronous version of , which executes the batch against its connection, returning a which can be used to access the results.
+
+ A task representing the asynchronous operation.
+
+
+ in . For more information about asynchronous programming, see [Asynchronous Programming](/dotnet/framework/data/adonet/asynchronous-programming).
+
+ This method stores in the task it returns all non-usage exceptions that the method's synchronous counterpart can throw. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+]]>
+
+
+ An error occurred while executing the batch.
+
+ The value is invalid.
+
+
+
+
+
+
+ Method
+
+
+ [System.Runtime.CompilerServices.NullableContext(2)]
+ [<System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+
+ System.Object
+
+
+
+ Executes the batch and returns the first column of the first row in the first returned result set. All other columns, rows and resultsets are ignored.
+ The first column of the first row in the first result set.
+ To be added.
+ An error occurred while executing the batch.
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task<System.Object>
+
+
+
+
+
+ A token to cancel the asynchronous operation.
+
+ An asynchronous version of , which executes the batch and returns the first column of the first row in the first returned result set. All other columns, rows and result sets are ignored.
+
+ The first column of the first row in the first result set.
+
+ This method stores in the task it returns all non-usage exceptions that the method's synchronous counterpart can throw. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+ An error occurred while executing the batch.
+
+
+
+
+
+ Method
+
+ System.Void
+
+
+
+ Creates a prepared (or compiled) version of the batch, or of each of its commands, on the data source.
+ To be added.
+
+
+
+
+
+ Method
+
+ System.Threading.Tasks.Task
+
+
+
+
+
+
+ An optional token to cancel the asynchronous operation. The default value is .
+
+ Asynchronously creates a prepared (or compiled) version of the batch, or of each of its commands, on the data source.
+
+ A representing the asynchronous operation.
+
+
+ This method stores in the task it returns all non-usage exceptions that the method's synchronous counterpart can throw. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by .
+
+
+
+
+
+
+ Property
+
+ System.Int32
+
+
+ Gets or sets the wait time (in seconds) before terminating the attempt to execute the batch and generating an error.
+ The time in seconds to wait for the batch to execute.
+
+
+ is generated if the assigned property value is less than 0.
+
+ Note to implementers: it's recommended that 0 mean no timeout.
+
+ ]]>
+
+
+
+
+
+
+
+ Property
+
+
+ [System.Runtime.CompilerServices.Nullable(2)]
+ [<System.Runtime.CompilerServices.Nullable(2)>]
+
+
+ [get: System.Runtime.CompilerServices.NullableContext(2)]
+ [<get: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+ [set: System.Runtime.CompilerServices.NullableContext(2)]
+ [<set: System.Runtime.CompilerServices.NullableContext(2)>]
+
+
+
+ System.Data.Common.DbTransaction
+
+
+
+ Gets or sets the within which this object executes.
+
+
+ The transaction within which a batch of a .NET data provider executes. The default value is a null reference ( in Visual Basic).
+
+ To be added.
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.Common/DbBatchCommand.xml b/doc/snippets/Microsoft.Data.Common/DbBatchCommand.xml
new file mode 100644
index 0000000000..68efa39676
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.Common/DbBatchCommand.xml
@@ -0,0 +1,113 @@
+
+
+
+
+ System.Object
+
+
+
+
+ [System.Runtime.CompilerServices.Nullable(0)]
+ [<System.Runtime.CompilerServices.Nullable(0)>]
+
+
+ [System.Runtime.CompilerServices.NullableContext(1)]
+ [<System.Runtime.CompilerServices.NullableContext(1)>]
+
+
+
+
+ Represents a single command within a . A batch can be executed against a data source in a single round trip.
+
+ To be added.
+
+
+
+
+
+ Constructor
+
+
+
+ Constructs an instance of the object.
+
+ To be added.
+
+
+
+
+
+ Property
+
+ System.String
+
+
+ Gets or sets the text command to run against the data source.
+ The text command to execute. The default value is an empty string ("").
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Data.CommandType
+
+
+
+ Gets or sets how the property is interpreted.
+
+
+ One of the enumeration values that specifies how a command string is interpreted. The default is .
+
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Data.Common.DbParameterCollection
+
+
+
+ Gets the collection of objects.
+
+ The parameters of the SQL statement or stored procedure.
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Data.Common.DbParameterCollection
+
+
+
+ Gets the collection of objects. For more information on parameters, see [Configuring Parameters and Parameter Data Types](/dotnet/framework/data/adonet/configuring-parameters-and-parameter-data-types).
+
+ The parameters of the SQL statement or stored procedure.
+ To be added.
+
+
+
+
+
+ Property
+
+ System.Int32
+
+
+
+ Gets the number of rows changed, inserted, or deleted by execution of this specific .
+
+ The number of rows changed, inserted, or deleted. -1 for SELECT statements; 0 if no rows were affected or the statement failed.
+ To be added.
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.Common/DbBatchCommandCollection.xml b/doc/snippets/Microsoft.Data.Common/DbBatchCommandCollection.xml
new file mode 100644
index 0000000000..610c7860f7
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.Common/DbBatchCommandCollection.xml
@@ -0,0 +1,410 @@
+
+
+
+
+ System.Object
+
+
+
+ System.Collections.Generic.ICollection<Microsoft.Data.Common.DbBatchCommand>
+
+
+ System.Collections.Generic.ICollection<T>
+
+
+ System.Collections.Generic.IEnumerable<Microsoft.Data.Common.DbBatchCommand>
+
+
+ System.Collections.Generic.IEnumerable<T>
+
+
+ System.Collections.Generic.IList<Microsoft.Data.Common.DbBatchCommand>
+
+
+ System.Collections.IEnumerable
+
+
+
+
+ [System.Runtime.CompilerServices.Nullable(0)]
+ [<System.Runtime.CompilerServices.Nullable(0)>]
+
+
+ [System.Runtime.CompilerServices.NullableContext(1)]
+ [<System.Runtime.CompilerServices.NullableContext(1)>]
+
+
+
+
+ The base class for a collection of instances of , contained in a .
+
+ To be added.
+
+
+
+
+ Constructor
+
+
+
+ Initializes a new instance of the class.
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.ICollection`1.Add(`0)
+
+
+ System.Void
+
+
+
+
+
+
+ The object to add to the .
+
+
+ Adds the specified object to the .
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.ICollection`1.Clear
+
+
+ System.Void
+
+
+
+
+ Removes all values from the .
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.ICollection`1.Contains(`0)
+
+
+ System.Boolean
+
+
+
+
+
+
+ The object to locate in the .
+
+
+ Indicates whether a is contained in the collection.
+
+
+ if the is in the collection; otherwise .
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.ICollection`1.CopyTo(`0[],System.Int32)
+
+
+ System.Void
+
+
+
+
+
+
+
+ The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
+
+
+ The zero-based index in at which copying begins.
+
+
+ Copies the elements of the to an , starting at a particular index.
+
+ To be added.
+
+
+
+
+
+ Property
+
+ P:System.Collections.Generic.ICollection`1.Count
+
+
+ System.Int32
+
+
+
+ Gets the number of elements contained in the .
+
+
+ The number of elements contained in the .
+
+ To be added.
+
+
+
+
+
+ Method
+
+ Microsoft.Data.Common.DbBatchCommand
+
+
+
+
+
+ To be added.
+ To be added.
+ To be added.
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.IEnumerable`1.GetEnumerator
+
+
+ System.Collections.Generic.IEnumerator<Microsoft.Data.Common.DbBatchCommand>
+
+
+
+
+ Returns the object at the specified index in the collection.
+
+
+ The object at the specified index in the collection.
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.IList`1.IndexOf(`0)
+
+
+ System.Int32
+
+
+
+
+
+
+ The object to locate in the .
+
+
+ Returns the index of the specified object.
+
+
+ The index of the specified object.
+
+ To be added.
+
+
+
+
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.IList`1.Insert(System.Int32,`0)
+
+
+ System.Void
+
+
+
+
+
+
+
+ The index at which to insert the object.
+
+
+ The object to insert into the .
+
+
+ Inserts the specified index of the object with the specified name into the collection at the specified index.
+
+ To be added.
+
+
+
+
+
+ Property
+
+ P:System.Collections.Generic.ICollection`1.IsReadOnly
+
+
+ System.Boolean
+
+
+ Specifies whether the collection is read-only.
+
+ if the collection is read-only; otherwise .
+
+ To be added.
+
+
+
+
+
+ Property
+
+ P:System.Collections.Generic.IList`1.Item(System.Int32)
+
+
+ Microsoft.Data.Common.DbBatchCommand
+
+
+
+
+
+
+ The zero-based index of the .
+
+
+ Gets or sets the at the specified index.
+
+
+ The at the specified index.
+
+ To be added.
+ The specified index does not exist.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.ICollection`1.Remove(`0)
+
+
+ System.Boolean
+
+
+
+
+
+
+ The object to remove from the .
+
+
+ Removes the specified object from the collection.
+
+
+ if was successfully removed; otherwise, . This method also returns if was not found in the .
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.Generic.IList`1.RemoveAt(System.Int32)
+
+
+ System.Void
+
+
+
+
+
+
+ The index where the object is located.
+
+
+ Removes the object at the specified from the collection.
+
+ To be added.
+
+
+
+
+
+ Method
+
+ System.Void
+
+
+
+
+
+
+
+ The index where the object is located.
+
+ To be added.
+
+ Sets the object at the specified index to a new value.
+
+ To be added.
+
+
+
+
+
+ Method
+
+ M:System.Collections.IEnumerable.GetEnumerator
+
+
+ System.Collections.IEnumerator
+
+
+
+ Returns an enumerator that iterates through a collection.
+
+ An object that can be used to iterate through the collection.
+
+
+
+ instance is cast to an interface.
+
+ ]]>
+
+
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml
new file mode 100644
index 0000000000..476a7a492e
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+ and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the `using` blocks fall out of scope.
+
+[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlBatch_ExecuteReader.cs#1)]
+]]>
+
+
+
+
+
+ Initializes a new .
+
+
+
+
+ and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the `using` blocks fall out of scope.
+
+[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlBatch_ExecuteReader.cs#1)]
+]]>
+
+
+
+
+ Initializes a new .
+
+
+ A
+
+ that represents the connection to an instance of SQL Server.
+
+
+ The
+
+ in which the
+
+ executes.
+
+
+
+
+
+
+
+
+ The list of commands contained in the batch in a .
+
+
+
+ and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the `using` blocks fall out of scope.
+
+[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlBatch_ExecuteReader.cs#1)]
+]]>
+
+
+
+
+
+ The list of commands contained in the batch in a .
+
+
+
+
+
+
+
+
+
+ Gets or sets the
+
+ used by this instance of the
+
+ .
+
+
+
+
+
+
+
+
+ Gets or sets the within which the commands execute.
+
+
+
+
+
+
+
+
+ Sends the
+
+ to the
+
+ and builds a
+
+ .
+
+
+
+ and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the `using` blocks fall out of scope.
+
+[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlBatch_ExecuteReader.cs#1)]
+]]>
+
+
+
+
+
+ An asynchronous version of
+
+ , which sends the
+
+ to the
+
+ and builds a
+
+ . Exceptions will be reported via the returned Task object.
+
+
+
+
+
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml
new file mode 100644
index 0000000000..9ad24b8abf
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml
@@ -0,0 +1,76 @@
+
+
+
+
+ Initializes a new .
+
+
+ and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the `using` blocks fall out of scope.
+
+[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlBatch_ExecuteReader.cs#1)]
+]]>
+
+
+
+
+ Initializes a new .
+ The text of the .
+ Indicates how the property is to be interpreted.
+ A , which is used to crate the .
+ The encryption setting. For more information, see [Always Encrypted](/sql/relational-databases/security/encryption/always-encrypted-database-engine).
+
+
+
+
+
+
+
+
+ Gets the
+
+ .
+
+ The parameters of the Transact-SQL statement or stored procedure. The default is an empty collection.
+
+
+
+
+
+
+
+
+ One of the values, indicating options for statement execution and data retrieval.
+
+
+
+
+
+
+
+
+
+ Not currently implemented.
+ The encryption setting. For more information, see [Always Encrypted](/sql/relational-databases/security/encryption/always-encrypted-database-engine).
+
+
+
+
+
+
+
+
+ Creates a new instance of a object.
+
+
+
+ Returns whether the method is implemented.
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommandCollection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommandCollection.xml
new file mode 100644
index 0000000000..f350a48731
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommandCollection.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ A collection of instances of , contained within a .
+
+
+
+
+
+
+
+
+ Add a to the end of the .
+
+
+
+
+
+
+
+
+ Determines whether a is in the .
+
+
+
+
+
+
+
+
+ Copies the entire to a one dimensional array, starting at the target index of the target array.
+
+
+
+
+
+
+
+
+ Searches for the specified within the and returns the zero-based index of the first occurence within the entire .
+
+ Returns the zero-based index of the first occurence within the entire .
+
+
+
+
+
+
+
+
+ Inserts an item into the at the specified index.
+
+
+
+
+
+
+
+
+ Removes the first occurence of the specific item from the .
+ Returns if an item is successfully removed. Returns false if an item could not be removed or no item was not found.
+
+
+
+
+
+
+
+ Gets or Sets the element at the specified index.
+ The element at the specified index.
+
+
+
+
+
+
+
+
+ Gets or Sets the element at the specified index.
+ The element at the specified index.
+
+
+
+
+
+
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientFactory.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientFactory.xml
index 6408d55a11..3947acf211 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientFactory.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientFactory.xml
@@ -38,6 +38,31 @@ The following example displays a list of all available SQL Server data sources,
]]>
+
+
+ Gets a value that indicates whether a
+
+ can be created.
+
+
+
+ if a
+
+ can be created; otherwise,
+
+ .
+
+
+
+ class provides the property so that inheritors can indicate
+whether they can provide a DbBatch. The displays this property, but its value is always `true`.
+]]>
+
+
+
Returns a strongly typed
@@ -227,6 +252,60 @@ DbParameter cmd = newFactory.CreateParameter();
To be added.
+
+
+ Returns a strongly typed
+
+ instance.
+
+
+ A new strongly typed instance of
+
+ .
+
+
+
+ instance:
+
+
+```csharp
+SqlClientFactory newFactory = SqlClientFactory.Instance;
+DbParameter cmd = newFactory.CreateBatch();
+```
+]]>
+
+
+
+
+
+ Returns a strongly typed
+
+ instance.
+
+
+ A new strongly typed instance of
+
+ .
+
+
+
+ instance:
+
+
+```csharp
+SqlClientFactory newFactory = SqlClientFactory.Instance;
+DbParameter cmd = newFactory.CreateBatchCommand();
+```
+]]>
+
+
+
Gets an instance of the
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlException.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlException.xml
index 9bb1ef18f3..b07ef7be4c 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlException.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlException.xml
@@ -292,6 +292,17 @@ catch (Exception ex) {
+
+ Gets the BatchCommand instance that generated the error or null if the exception was not raised from a batch.
+ A BatchCommand object or null
+
+
+
+
+
+
+
+ Gets a numeric error code from SQL Server that represents an error, warning or "no data found" message. For more information about how to decode these values, see Database Engine Events and Errors.The number representing the error code.
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index 327b6f4ff9..e1022d786e 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -98,6 +98,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient",
..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationParameters.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationParameters.xml
..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationProvider.xml
..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationToken.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationToken.xml
+ ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatch.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatch.xml
+ ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommand.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommand.xml
+ ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommandCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommandCollection.xml
..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopy.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopy.xml
..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMapping.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMapping.xml
..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMappingCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMappingCollection.xml
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
index 96308eae65..4bee03a8e0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj
@@ -16,12 +16,16 @@
-
-
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index c094cf766d..21454d70ec 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -273,6 +273,15 @@
Microsoft\Data\SqlClient\SqlAuthenticationToken.cs
+
+ Microsoft\Data\SqlClient\SqlBatch.cs
+
+
+ Microsoft\Data\SqlClient\SqlBatchCommand.cs
+
+
+ Microsoft\Data\SqlClient\SqlBatchCommandCollection.cs
+
Microsoft\Data\SqlClient\SqlBulkCopyColumnMapping.cs
@@ -956,6 +965,14 @@
Common\System\Net\Security\NegotiateStreamPal.Unix.cs
+
+
+
+
+
+ Microsoft\Data\SqlClient\SqlBatchCommand.Net8OrGreater.cs
+
+
@@ -991,4 +1008,4 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientFactory.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientFactory.NetCoreApp.cs
new file mode 100644
index 0000000000..4b0d4a8b2d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientFactory.NetCoreApp.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Data.Common;
+
+namespace Microsoft.Data.SqlClient
+{
+ public sealed partial class SqlClientFactory : DbProviderFactory
+ {
+ ///
+ public override bool CanCreateBatch => true;
+
+ ///
+ public override DbBatch CreateBatch() => new SqlBatch();
+
+ ///
+ public override DbBatchCommand CreateBatchCommand() => new SqlBatchCommand();
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
index 3e12b61a61..39dbb917e0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs
@@ -391,9 +391,7 @@ private CachedAsyncState cachedAsyncState
private bool _batchRPCMode;
private List<_SqlRPC> _RPCList;
- private _SqlRPC[] _SqlRPCBatchArray;
private _SqlRPC[] _sqlRPCParameterEncryptionReqArray;
- private List _parameterCollectionList;
private int _currentlyExecutingBatch;
private SqlRetryLogicBaseProvider _retryLogicProvider;
@@ -1142,7 +1140,7 @@ public override object ExecuteScalar()
RunExecuteReaderWithRetry(0, RunBehavior.ReturnImmediately, returnStream: true) :
RunExecuteReader(0, RunBehavior.ReturnImmediately, returnStream: true, method: nameof(ExecuteScalar));
success = true;
- return CompleteExecuteScalar(ds, false);
+ return CompleteExecuteScalar(ds, _batchRPCMode);
}
catch (Exception ex)
{
@@ -1161,26 +1159,22 @@ public override object ExecuteScalar()
}
}
- private object CompleteExecuteScalar(SqlDataReader ds, bool returnSqlValue)
+ private object CompleteExecuteScalar(SqlDataReader ds, bool returnLastResult)
{
object retResult = null;
try
{
- if (ds.Read())
+ do
{
- if (ds.FieldCount > 0)
+ if (ds.Read())
{
- if (returnSqlValue)
- {
- retResult = ds.GetSqlValue(0);
- }
- else
+ if (ds.FieldCount > 0)
{
retResult = ds.GetValue(0);
}
}
- }
+ } while (returnLastResult && ds.NextResult());
}
finally
{
@@ -1407,7 +1401,7 @@ private void VerifyEndExecuteState(Task completionTask, string endMethod, bool f
{
_stateObj.Parser.State = TdsParserState.Broken; // We failed to respond to attention, we have to quit!
_stateObj.Parser.Connection.BreakConnection();
- _stateObj.Parser.ThrowExceptionAndWarning(_stateObj);
+ _stateObj.Parser.ThrowExceptionAndWarning(_stateObj, this);
}
else
{
@@ -1678,7 +1672,7 @@ private Task InternalExecuteNonQuery(TaskCompletionSource
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs
new file mode 100644
index 0000000000..342244ed9c
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs
@@ -0,0 +1,623 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Data;
+using System.Data.Common;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public static class BatchTests
+ {
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void MissingCommandTextThrows()
+ {
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (var batch = new SqlBatch { Connection = connection, BatchCommands = { new SqlBatchCommand() } })
+ {
+ connection.Open();
+ Assert.Throws(() => batch.ExecuteNonQuery());
+ }
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void MissingConnectionThrows()
+ {
+ using (var batch = new SqlBatch { BatchCommands = { new SqlBatchCommand("SELECT @@SPID") } })
+ {
+ Assert.Throws(() => batch.ExecuteNonQuery());
+ }
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void StoredProcedureBatchSupported()
+ {
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (var batch = new SqlBatch { Connection = connection, BatchCommands = { new SqlBatchCommand("sp_help", CommandType.StoredProcedure) } })
+ {
+ connection.Open();
+ batch.ExecuteNonQuery();
+ }
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void CommandTextBatchSupported()
+ {
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (var batch = new SqlBatch { Connection = connection, BatchCommands = { new SqlBatchCommand("select @@SPID", CommandType.Text) } })
+ {
+ connection.Open();
+ batch.ExecuteNonQuery();
+ }
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void TableDirectBatchNotSupported()
+ {
+ Assert.Throws(() => new SqlBatchCommand("Categories", CommandType.TableDirect));
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void MixedBatchSupported()
+ {
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (var batch = new SqlBatch
+ {
+ Connection = connection,
+ BatchCommands =
+ {
+ new SqlBatchCommand("select @@SPID", CommandType.Text),
+ new SqlBatchCommand("sp_help",CommandType.StoredProcedure)
+ }
+ })
+ {
+ connection.Open();
+ batch.ExecuteNonQuery();
+ }
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void DisposedThrows()
+ {
+ var batch = new SqlBatch { BatchCommands = { new SqlBatchCommand("SELECT @@SPID") } };
+ batch.Dispose();
+ Assert.Throws(() => batch.ExecuteNonQuery());
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ProviderApi()
+ {
+ decimal foundFreight = 0.0m;
+ int resultCount = 0;
+ int rowCount = 0;
+ var dbProviderFactory = SqlClientFactory.Instance;
+ DbBatch batch = dbProviderFactory.CreateBatch();
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ {
+ DbParameter p1 = dbProviderFactory.CreateParameter();
+ DbParameter p2 = dbProviderFactory.CreateParameter();
+ p1.ParameterName = "@p1";
+ p2.ParameterName = "@p2";
+ p1.Value = 50.0f;
+ p2.Value = 10248;
+ DbBatchCommand command = dbProviderFactory.CreateBatchCommand();
+ command.CommandText = "UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2";
+ command.Parameters.Add(p1);
+ command.Parameters.Add(p2);
+ batch.BatchCommands.Add(command);
+ }
+
+ {
+ DbParameter parameter = dbProviderFactory.CreateParameter();
+ parameter.ParameterName = "@p4";
+ parameter.Value = 10248;
+ DbBatchCommand command = dbProviderFactory.CreateBatchCommand();
+ command.CommandText = $"SELECT Freight FROM Orders WHERE OrderID={parameter.ParameterName}";
+ command.Parameters.Add(parameter);
+ batch.BatchCommands.Add(command);
+ }
+
+ batch.Connection = connection;
+ batch.Transaction = transaction;
+
+ try
+ {
+ using (var reader = batch.ExecuteReader())
+ {
+ do
+ {
+ resultCount += 1;
+ while (reader.Read())
+ {
+ foundFreight = reader.GetDecimal(0);
+ rowCount += 1;
+ }
+ }
+ while (reader.NextResult());
+ }
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+
+ Assert.Equal(1, resultCount);
+ Assert.Equal(1, rowCount);
+ Assert.Equal(50.0m, foundFreight);
+
+ Assert.Equal(1, batch.BatchCommands[0].RecordsAffected);
+ Assert.Equal(0, batch.BatchCommands[1].RecordsAffected);
+
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void DirectApi()
+ {
+ decimal foundFreight = 0.0m;
+ int resultCount = 0;
+ int rowCount = 0;
+
+ SqlException exception = null;
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ var batch = new SqlBatch
+ {
+ Connection = connection,
+ Transaction = transaction,
+ BatchCommands =
+ {
+ new SqlBatchCommand("UPDATE table SET f1=@p1 WHERE f2=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("p1", 8),
+ new SqlParameter("p2", 9),
+ }
+ },
+ new SqlBatchCommand("SELECT * FROM [does not exist] WHERE f2=@p1")
+ {
+ Parameters =
+ {
+ new SqlParameter("p1", 8),
+ }
+ }
+ }
+ };
+
+ using (batch)
+ {
+ try
+ {
+ using (var reader = batch.ExecuteReader())
+ {
+ do
+ {
+ resultCount += 1;
+ while (reader.Read())
+ {
+ foundFreight = reader.GetDecimal(0);
+ rowCount += 1;
+ }
+ }
+ while (reader.NextResult());
+ }
+ }
+ catch (SqlException sqlex)
+ {
+ exception = sqlex;
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+ }
+
+ Assert.NotNull(exception);
+ Assert.NotNull(exception.BatchCommand);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ExceptionInBatchContainsBatch()
+ {
+ SqlException exception = null;
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ using (var batch = new SqlBatch
+ {
+ Connection = connection,
+ Transaction = transaction,
+ BatchCommands = { new SqlBatchCommand("RAISERROR ( 'an intentional error occured.', 15, 1)") }
+ }
+ )
+ {
+ try
+ {
+ batch.ExecuteNonQuery();
+ }
+ catch (SqlException sqlex)
+ {
+ exception = sqlex;
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+ }
+
+ Assert.NotNull(exception);
+ Assert.NotNull(exception.BatchCommand);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ExceptionWithoutBatchContainsNoBatch()
+ {
+ SqlException exception = null;
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ using (var command = new SqlCommand("RAISERROR ( 'an intentional error occured.', 15, 1)", connection, transaction))
+ {
+ try
+ {
+ command.ExecuteNonQuery();
+ }
+ catch (SqlException sqlex)
+ {
+ exception = sqlex;
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+
+ Assert.NotNull(exception);
+ Assert.Null(exception.BatchCommand);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ParameterInOutAndReturn()
+ {
+ string create =
+ @"
+CREATE PROCEDURE TestInAndOutParams
+ @Input int,
+ @InOut int OUTPUT,
+ @Output int = default OUTPUT
+AS
+BEGIN
+ SET NOCOUNT ON;
+ SELECT @InOut = 2 * @InOut, @Output = 2 * @Input
+ RETURN @Input
+END";
+ string drop = "DROP PROCEDURE TestInAndOutParams";
+
+ SqlParameter input = CreateParameter("@Input", SqlDbType.Int, 2);
+ SqlParameter inputOutput = CreateParameter("@InOut", SqlDbType.Int, 4, ParameterDirection.InputOutput);
+ SqlParameter output = CreateParameter("@Output", SqlDbType.Int, DBNull.Value, ParameterDirection.Output);
+ SqlParameter returned = CreateParameter("@RETURN_VALUE", SqlDbType.Int, DBNull.Value, ParameterDirection.ReturnValue);
+ try
+ {
+ TryExecuteNonQueryCommand(drop);
+ ExecuteNonQueryCommand(create);
+
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlBatch batch = new SqlBatch(conn))
+ {
+ conn.Open();
+ batch.Commands.Add(new SqlBatchCommand("SELECT @@VERSION"));
+ batch.Commands.Add(
+ new SqlBatchCommand(
+ "TestInAndOutParams",
+ CommandType.StoredProcedure,
+ new[] { input, inputOutput, output, returned }
+ )
+ );
+ batch.Commands.Add(new SqlBatchCommand("SELECT @@SPID"));
+ batch.ExecuteNonQuery();
+ }
+ }
+ finally
+ {
+ TryExecuteNonQueryCommand(drop);
+ }
+
+ Assert.Equal(8, Convert.ToInt32(inputOutput.Value));
+ Assert.Equal(4, Convert.ToInt32(output.Value));
+ Assert.Equal(2, Convert.ToInt32(returned.Value));
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ExecuteNonQuery()
+ {
+ int count = 0;
+
+ var batch = new SqlBatch
+ {
+ BatchCommands =
+ {
+ new SqlBatchCommand("UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 50.0f),
+ new SqlParameter("@p2", 10248),
+ }
+ },
+ new SqlBatchCommand($"UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 36.0f),
+ new SqlParameter("@p2", -10248),
+ }
+ },
+ new SqlBatchCommand($"UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 90.0f),
+ new SqlParameter("@p2", 10248),
+ }
+ }
+ }
+ };
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+
+ batch.Connection = connection;
+ batch.Transaction = transaction;
+
+ try
+ {
+ count = batch.ExecuteNonQuery();
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+
+ Assert.Equal(3, batch.Commands.Count);
+ Assert.Equal(2, count);
+ Assert.Equal(1, batch.Commands[0].RecordsAffected);
+ Assert.Equal(0, batch.Commands[1].RecordsAffected);
+ Assert.Equal(1, batch.Commands[2].RecordsAffected);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static async Task ExecuteNonQueryAsync()
+ {
+ int count = 0;
+
+ var batch = new SqlBatch
+ {
+ BatchCommands =
+ {
+ new SqlBatchCommand("UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 50.0f),
+ new SqlParameter("@p2", 10248),
+ }
+ },
+ new SqlBatchCommand($"UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 36.0f),
+ new SqlParameter("@p2", -10248),
+ }
+ },
+ new SqlBatchCommand($"UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2")
+ {
+ Parameters =
+ {
+ new SqlParameter("@p1", 90.0f),
+ new SqlParameter("@p2", 10248),
+ }
+ }
+ }
+ };
+ using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ await connection.OpenAsync();
+ using (var transaction = connection.BeginTransaction())
+ {
+
+ batch.Connection = connection;
+ batch.Transaction = (SqlTransaction)transaction;
+
+ try
+ {
+ count = await batch.ExecuteNonQueryAsync();
+ }
+ finally
+ {
+ transaction.Rollback();
+ }
+ }
+ }
+
+ Assert.Equal(3, batch.Commands.Count);
+ Assert.Equal(2, count);
+ Assert.Equal(1, batch.Commands[0].RecordsAffected);
+ Assert.Equal(0, batch.Commands[1].RecordsAffected);
+ Assert.Equal(1, batch.Commands[2].RecordsAffected);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ExecuteScalarMultiple()
+ {
+ int value = 0;
+
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlBatch batch = new SqlBatch(conn))
+ {
+ conn.Open();
+ for (int index = 0; index < 10; index++)
+ {
+ batch.Commands.Add(new SqlBatchCommand($"SELECT {index}"));
+ }
+ value = Convert.ToInt32(batch.ExecuteScalar());
+ }
+
+ Assert.Equal(9, value);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static async Task ExecuteScalarAsyncMultiple()
+ {
+ int value = 0;
+
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlBatch batch = new SqlBatch(conn))
+ {
+ await conn.OpenAsync();
+ for (int index = 0; index < 10; index++)
+ {
+ batch.Commands.Add(new SqlBatchCommand($"SELECT {index}"));
+ }
+ value = Convert.ToInt32(await batch.ExecuteScalarAsync());
+ }
+
+ Assert.Equal(9, value);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static void ExecuteReaderMultiple()
+ {
+ int resultSetCount = 0;
+ int resultRowCount = 0;
+
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlBatch batch = new SqlBatch(conn))
+ {
+ conn.Open();
+ for (int index = 0; index < 10; index++)
+ {
+ batch.Commands.Add(new SqlBatchCommand($"SELECT {index}"));
+ }
+ using (var reader = batch.ExecuteReader())
+ {
+ do
+ {
+ resultSetCount += 1;
+ while (reader.Read())
+ {
+ resultRowCount += 1;
+ }
+ } while (reader.NextResult());
+ }
+ }
+
+ Assert.Equal(10, resultSetCount);
+ Assert.Equal(10, resultRowCount);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static async Task ExecuteReaderAsyncMultiple()
+ {
+ int resultSetCount = 0;
+ int resultRowCount = 0;
+
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlBatch batch = new SqlBatch(conn))
+ {
+ await conn.OpenAsync();
+ for (int index = 0; index < 10; index++)
+ {
+ batch.Commands.Add(new SqlBatchCommand($"SELECT {index}"));
+ }
+ using (var reader = await batch.ExecuteReaderAsync())
+ {
+ do
+ {
+ resultSetCount += 1;
+ while (await reader.ReadAsync())
+ {
+ resultRowCount += 1;
+ }
+ } while (await reader.NextResultAsync());
+ }
+ }
+
+ Assert.Equal(10, resultSetCount);
+ Assert.Equal(10, resultRowCount);
+ }
+
+ private static SqlParameter CreateParameter(string name, SqlDbType type, T value, ParameterDirection direction = ParameterDirection.Input)
+ {
+ var parameter = new SqlParameter(name, type);
+ parameter.Direction = direction;
+ parameter.Value = value;
+ return parameter;
+ }
+
+ private static void ExecuteNonQueryCommand(string command)
+ {
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using (SqlCommand cmd = conn.CreateCommand())
+ {
+ conn.Open();
+ cmd.CommandText = command;
+ cmd.ExecuteNonQuery();
+ }
+ }
+ private static bool TryExecuteNonQueryCommand(string command)
+ {
+ try
+ {
+ ExecuteNonQueryCommand(command);
+ return true;
+ }
+ catch
+ {
+ }
+ return false;
+ }
+ }
+}