Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 79 additions & 3 deletions doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<docs>
<docs>
<members name="SqlBatch">
<SqlBatch>
<example>
Expand Down Expand Up @@ -179,7 +179,7 @@
The list of <see cref="T:Microsoft.Data.SqlClient.SqlBatchCommand" /> contained in the batch in a <see cref="T:System.Collections.Generic.List`1" />.
</summary>
</Commands>
<ExecuteReader>
<ExecuteReader name="NoParameter">
<summary>
Sends the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Commands" /> to the <see cref="P:Microsoft.Data.SqlClient.SqlCommand.Connection" /> and builds a <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" />.
</summary>
Expand Down Expand Up @@ -232,7 +232,83 @@
</code>
</example>
</ExecuteReader>
<ExecuteReaderAsync>
<ExecuteReader name="BehaviorParameter">
<param name="behavior">
An instance of <see cref="T:System.Data.CommandBehavior" />, specifying options for batch execution and data retrieval.
</param>
<summary>
Sends the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Commands" /> to the <see cref="P:Microsoft.Data.SqlClient.SqlCommand.Connection" /> and builds a <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" />.
</summary>
<example>
<para>
The following example creates a <see cref="T:Microsoft.Data.SqlClient.SqlConnection" /> and a <see cref="T:Microsoft.Data.SqlClient.SqlBatch" />, then adds multiple <see cref="T:Microsoft.Data.SqlClient.SqlBatchCommand" /> objects to the batch. It then executes the batch, creating a <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" />. The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" /> and then the <see cref="T:Microsoft.Data.SqlClient.SqlConnection" /> as the <c>using</c> blocks fall out of scope.
</para>
<code language="c#">
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();

using var batch = new SqlBatch(connection);

const int count = 10;
const string parameterName = "parameter";
for (int i = 0; i &lt; count; i++)
{
var batchCommand = new SqlBatchCommand($"SELECT @{parameterName} as value");
batchCommand.Parameters.Add(new SqlParameter(parameterName, i));
batch.BatchCommands.Add(batchCommand);
}

var results = new List&lt;int&gt;(count);
using (SqlDataReader reader = batch.ExecuteReader())
Comment thread
campersau marked this conversation as resolved.
Outdated
{
do
{
while (reader.Read())
{
results.Add(reader.GetFieldValue&lt;int&gt;(0));
}
} while (reader.NextResult());
}
Console.WriteLine(string.Join(", ", results));
}
}
</code>
</example>
</ExecuteReader>
<ExecuteReaderAsync name="NoParameter">
<param name="cancellationToken">A token to cancel the asynchronous operation.</param>
<summary>
An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlBatch.ExecuteReader" />, which sends the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Commands" /> to the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Connection" /> and builds a <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" />.
Exceptions will be reported via the returned Task object.
</summary>
<returns>A task representing the asynchronous operation.</returns>
<exception cref="T:Microsoft.Data.SqlClient.SqlException">
An error occurred while executing the batch.
</exception>
<exception cref="T:System.ArgumentException">
The <see cref="T:System.Data.CommandBehavior" /> value is invalid.
</exception>
<exception cref="T:System.OperationCanceledException">
The cancellation token was canceled. This exception is stored into the returned task.
</exception>
</ExecuteReaderAsync>
<ExecuteReaderAsync name="BehaviorParameter">
<param name="behavior">
An instance of <see cref="T:System.Data.CommandBehavior" />, specifying options for batch execution and data retrieval.
</param>
<param name="cancellationToken">A token to cancel the asynchronous operation.</param>
<summary>
An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlBatch.ExecuteReader" />, which sends the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Commands" /> to the <see cref="P:Microsoft.Data.SqlClient.SqlBatch.Connection" /> and builds a <see cref="T:Microsoft.Data.SqlClient.SqlDataReader" />.
Expand Down
16 changes: 14 additions & 2 deletions src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,26 @@ public class SqlBatch : System.IDisposable, System.IAsyncDisposable
#else
public System.Threading.Tasks.Task<int> ExecuteNonQueryAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
#endif
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader/*'/>
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader[@name="NoParameter"]/*'/>
public Microsoft.Data.SqlClient.SqlDataReader ExecuteReader() => throw null;
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync/*'/>
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader[@name="BehaviorParameter"]/*'/>
#if NET
public new Microsoft.Data.SqlClient.SqlDataReader ExecuteReader(System.Data.CommandBehavior behavior) => throw null;
#else
public Microsoft.Data.SqlClient.SqlDataReader ExecuteReader(System.Data.CommandBehavior behavior) => throw null;
#endif
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync[@name="NoParameter"]/*'/>
#if NET
public new System.Threading.Tasks.Task<Microsoft.Data.SqlClient.SqlDataReader> ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
#else
public System.Threading.Tasks.Task<Microsoft.Data.SqlClient.SqlDataReader> ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
#endif
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync[@name="BehaviorParameter"]/*'/>
#if NET
public new System.Threading.Tasks.Task<Microsoft.Data.SqlClient.SqlDataReader> ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken = default) => throw null;
#else
public System.Threading.Tasks.Task<Microsoft.Data.SqlClient.SqlDataReader> ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken = default) => throw null;
#endif
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteScalar/*'/>
#if NET
public override object ExecuteScalar() => throw null;
Expand Down
Comment thread
campersau marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -266,24 +266,54 @@ SqlTransaction Transaction
}
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader/*'/>
public SqlDataReader ExecuteReader()
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader[@name="NoParameter"]/*'/>
public SqlDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default);

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReader[@name="BehaviorParameter"]/*'/>
public
#if NET
new
#endif
SqlDataReader ExecuteReader(CommandBehavior behavior)
{
ValidateExecuteCommandBehavior(nameof(ExecuteReader), behavior);

CheckDisposed();
SetupBatchCommandExecute();
return _batchCommand.ExecuteReader();
return _batchCommand.ExecuteReader(behavior);
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync/*'/>
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync[@name="NoParameter"]/*'/>
public
#if NET
new
#endif
Task<SqlDataReader> ExecuteReaderAsync(CancellationToken cancellationToken = default)
Task<SqlDataReader> ExecuteReaderAsync(CancellationToken cancellationToken = default) => ExecuteReaderAsync(CommandBehavior.Default, cancellationToken);

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteReaderAsync[@name="BehaviorParameter"]/*'/>
public
#if NET
new
#endif
Task<SqlDataReader> ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken = default)
{
ValidateExecuteCommandBehavior(nameof(ExecuteReaderAsync), behavior);

CheckDisposed();
SetupBatchCommandExecute();
return _batchCommand.ExecuteReaderAsync(cancellationToken);
return _batchCommand.ExecuteReaderAsync(behavior, cancellationToken)
.ContinueWith((result) =>
Comment thread
campersau marked this conversation as resolved.
Outdated
{
if (result.IsFaulted)
{
throw result.Exception.InnerException;
}
return result.Result;
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled,
TaskScheduler.Default
);
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteDbDataReader/*'/>
Expand All @@ -293,7 +323,7 @@ Task<SqlDataReader> ExecuteReaderAsync(CancellationToken cancellationToken = def
#else
virtual
#endif
DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader();
DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(behavior);

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/ExecuteDbDataReaderAsync/*'/>
protected
Expand All @@ -302,24 +332,7 @@ Task<SqlDataReader> ExecuteReaderAsync(CancellationToken cancellationToken = def
#else
virtual
#endif
Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
CheckDisposed();
SetupBatchCommandExecute();
return _batchCommand.ExecuteReaderAsync(cancellationToken)
.ContinueWith<DbDataReader>((result) =>
{
if (result.IsFaulted)
{
throw result.Exception.InnerException;
}
return result.Result;
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled,
TaskScheduler.Default
);
}
async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) => await ExecuteReaderAsync(behavior, cancellationToken);
Comment thread
campersau marked this conversation as resolved.

private void CheckDisposed()
{
Expand Down Expand Up @@ -350,5 +363,30 @@ private void SetupBatchCommandExecute()
}
_batchCommand.SetBatchRPCModeReadyToExecute();
}

/// <summary>
/// Validates that the provided <see cref="CommandBehavior"/> is compatible with <see cref="SqlBatch"/>.
/// </summary>
/// <param name="method">The name of the calling method for error reporting.</param>
/// <param name="behavior">The behavior flags to validate.</param>
/// <remarks>
/// <para>
/// Only <see cref="CommandBehavior.SequentialAccess"/> and <see cref="CommandBehavior.CloseConnection"/>
/// are supported at the batch level.
/// </para>
/// <para>
/// To apply other behaviors (such as <see cref="CommandBehavior.SingleRow"/> or <see cref="CommandBehavior.SchemaOnly"/>),
/// they must be set on individual <see cref="SqlBatchCommand"/> instances within the batch.
/// </para>
/// </remarks>
/// <exception cref="NotSupportedException">Thrown when unsupported behavior flags are detected.</exception>
internal static void ValidateExecuteCommandBehavior(string method, CommandBehavior behavior)
{
if (0 != (behavior & ~(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection)))
{
ADP.ValidateCommandBehavior(behavior);
throw ADP.NotSupportedCommandBehavior(behavior & ~(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection), method);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal void AddBatchCommand(SqlBatchCommand batchCommand)
{
// All batch sql statements must be executed inside sp_executesql, including those
// without parameters
BuildExecuteSql(CommandBehavior.Default, commandText, batchCommand.Parameters, ref rpc);
BuildExecuteSql(batchCommand.CommandBehavior, commandText, batchCommand.Parameters, ref rpc);
}

_RPCList.Add(rpc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,13 +463,13 @@ private void BuildExecuteSql(
SqlParameter sqlParam;

// @batch_text
commandText ??= GetCommandText(behavior);
string text = GetCommandText(behavior) + GetOptionsResetString(behavior);
Comment thread
campersau marked this conversation as resolved.
Outdated
sqlParam = rpc.systemParams[0];
sqlParam.SqlDbType = (commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT
sqlParam.SqlDbType = (text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT
? SqlDbType.NVarChar
: SqlDbType.NText;
sqlParam.Size = commandText.Length;
sqlParam.Value = commandText;
sqlParam.Size = text.Length;
sqlParam.Value = text;
sqlParam.Direction = ParameterDirection.Input;

// @batch_params
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,97 @@ public static async Task ExecuteReaderAsyncMultiple()
Assert.Equal(10, resultRowCount);
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void ExecuteReaderCommandCommandBehaviorSchemaOnlyKeyInfo()
Comment thread
campersau marked this conversation as resolved.
{
System.Collections.ObjectModel.ReadOnlyCollection<DbColumn> schema;

using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
using (SqlBatch batch = new SqlBatch(conn))
{
conn.Open();

var cmd = new SqlBatchCommand("SELECT * FROM Categories");
cmd.CommandBehavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
Comment thread
campersau marked this conversation as resolved.
batch.BatchCommands.Add(cmd);

using var reader = batch.ExecuteReader();

Assert.False(reader.Read());

schema = reader.GetColumnSchema();
}

Assert.Equal(4, schema.Count);
Assert.True(schema[0].IsKey);
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void ExecuteReaderCommandBehaviorCloseConnection()
{
int resultSetCount = 0;
int resultRowCount = 0;

using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
using (SqlBatch batch = new SqlBatch(conn))
{
conn.Open();

batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1"));
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 2"));

using (var reader = batch.ExecuteReader(CommandBehavior.CloseConnection))
{
do
{
resultSetCount += 1;
while (reader.Read())
{
resultRowCount += 1;
}
} while (reader.NextResult());
}

Assert.Equal(ConnectionState.Closed, conn.State);
}

Assert.Equal(2, resultSetCount);
Assert.Equal(2, resultRowCount);
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static async Task ExecuteReaderAsyncCommandBehaviorCloseConnection()
{
int resultSetCount = 0;
int resultRowCount = 0;

using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
await using (SqlBatch batch = new SqlBatch(conn))
{
await conn.OpenAsync();

batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1"));
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 2"));

using (var reader = await batch.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
do
{
resultSetCount += 1;
while (await reader.ReadAsync())
{
resultRowCount += 1;
}
} while (await reader.NextResultAsync());
}

Assert.Equal(ConnectionState.Closed, conn.State);
}

Assert.Equal(2, resultSetCount);
Assert.Equal(2, resultRowCount);
}

private static SqlParameter CreateParameter<T>(string name, SqlDbType type, T value, ParameterDirection direction = ParameterDirection.Input)
{
var parameter = new SqlParameter(name, type);
Expand Down
Loading
Loading