I have a block of code where I want to apply the using
statement to each command in the commands
enumerable. What is the C# syntax for this?
await using var transaction = await conn.BeginTransactionAsync(cancel);
IEnumerable<DbCommand> commands = BuildSnowflakeCommands(conn, tenantId);
var commandTasks = new List<Task>();
foreach (var command in commands)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}
try
{
await Task.WhenAll(commandTasks);
}
catch (SnowflakeDbException)
{
await transaction.RollbackAsync(cancel);
return;
}
await transaction.CommitAsync(cancel);
Edit: Rider / ReSharper seems to think these two are equivalent, or at least I get a prompt to convert the for
into a foreach
(this is clearly wrong):
for (var i = 0; i < commands.Count; i++)
{
await using var command = commands[i];
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}
and
foreach (var command in commands)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}
Edit 2: After some discussion and a few helpful answers, this is what I'm going with:
var transaction = await conn.BeginTransactionAsync(cancel);
var commands = BuildSnowflakeCommands(conn, tenantId);
var commandTasks = commands.Select(async command =>
{
await using (command)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
await command.ExecuteNonQueryAsync(cancel);
}
});
try
{
await Task.WhenAll(commandTasks);
await transaction.CommitAsync(cancel);
}
catch (SnowflakeDbException)
{
await transaction.RollbackAsync(cancel);
}
finally
{
await transaction.DisposeAsync();
}
IAsyncEnumerable<T> exposes an enumerator that has a MoveNextAsync() method that can be awaited. This means a method that produces this result can make asynchronous calls in between yielding results.
WhenAll(IEnumerable<Task>) Creates a task that will complete when all of the Task objects in an enumerable collection have completed. WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.
This method is used when you have a bunch of tasks that you want to await all at once. We then await the method and wait for all the Tasks in our collection to complete. Once done, the method returns its Task as completed to its caller and our logic is complete.
C# Language Async-Await Async/await will only improve performance if it allows the machine to do additional work.
You can use LINQ:
var commandTasks = commands.Select(async command =>
{
using (command)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
await command.ExecuteNonQueryAsync(cancel);
}
});
The command will then be disposed as soon as it exits scope.
Full code:
await using var transaction = await conn.BeginTransactionAsync(cancel);
IEnumerable<DbCommand> commands = BuildSnowflakeCommands(conn, tenantId);
var commandTasks = commands.Select(async command =>
{
using (command)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
await command.ExecuteNonQueryAsync(cancel);
}
});
try
{
await Task.WhenAll(commandTasks);
}
catch (SnowflakeDbException)
{
await transaction.RollbackAsync(cancel);
return;
}
await transaction.CommitAsync(cancel);
Definitely don't use the for
loop example; the await
will cause each command to happen in serial, as the completion of each query must be awaited before the next is initiated.
You can't call Dispose
or DisposeAsync
a set of IDisposable
s or IAsyncDisposable
s using the language syntax alone.
You can iterate each one and call the appropriate method. I would cache all the commands as an array or readonly collection before re-enumerating.
I would avoid disposing in your loop as your JetBrains tooling recommends. The command has to live longer than just that.
Personally, I would do something like this:
var commands = BuildSnowflakeCommands(conn, tenantId).ToArray();
var commandTasks = new List<Task>(commands.Length);
foreach (var command in commands)
{
command.CommandTimeout = commandTimeout;
command.Transaction = transaction;
commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}
/// later...
foreach (var command in commands)
{
command.Dispose();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With