My approach to executing SQL scripts is different from previously mentioned answers due to:
- Usage of the using statement, which ensures the correct use of disposable objects.
- Takes into account the SQL Transactions class. This allows the script contents to be executed but can be rolled back on any error allowing better error handling.
- Makes use of the ArgumentNullException class that helps prevent exceptions regarding nullable arguments like
connectionString
or scriptPath
.
- Allows correct execution of scripts containing
GO
keyword, preventing the following Exception: System.Data.SqlClient.SqlException (0x80131904): Incorrect syntax near 'GO'.
.
The previous answers are sufficient, but I think this approach is a viable option SQL Transactions preventing incomplete scripts being executed on to an SQL database leading to erroneos data modifications.
public async Task ApplySqlCommandAsync(string connectionString, string scriptPath)
{
ArgumentNullException.ThrowIfNull(connectionString, nameof(connectionString));
ArgumentNullException.ThrowIfNull(scriptPath, nameof(scriptPath));
var scriptContents = await ReadAllTextAsync(scriptPath);
_logger.LogInformation("Opening connection");
await using SqlConnection sqlConnection = new(connectionString);
await sqlConnection.OpenAsync();
SqlTransaction sqlTransaction = sqlConnection.BeginTransaction();
try
{
_logger.LogInformation(
"Executing script: {ScriptPath}",
scriptPath
);
// split script on GO command
IEnumerable<string> commandStrings = Regex.Split(
scriptContents,
@"^\s*GO\s*$",
RegexOptions.Multiline | RegexOptions.IgnoreCase
);
if (commandStrings != null && commandStrings.Any())
{
foreach (var commandString in commandStrings)
{
if (commandString.Trim() == string.Empty)
continue;
await using var command = new SqlCommand(commandString, sqlConnection);
command.Transaction = sqlTransaction;
await command.ExecuteNonQueryAsync();
}
}
else
{
_logger.LogInformation(
"Could not obtain any {Entity} from script {ScriptPath}",
nameof(SqlCommand),
scriptPath
);
}
await sqlTransaction.CommitAsync();
_logger.LogInformation(
"Successfully executed script: {ScriptPath}",
scriptPath
);
}
catch (SqlException sqlException)
{
_logger.LogError(
sqlException,
"Failed at {Now}. Reason: {Reason}",
DateTimeOffset.UtcNow,
sqlException.Message
);
await sqlTransaction.RollbackAsync();
throw;
}
}
I had the need to use Transactions, also added the fix for the 'GO' command from this post