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