在SqlException上自动重试请求

时间:2016-03-08 09:55:33

标签: c# .net sql-server

我必须修改用于处理SQL调用的静态类,以便在有特定SqlException(例如连接丢失)的情况下重试请求。

以下是我用于调用存储过程的方法:

public static int CallExecuteNonQuery(string storeProcName, Action<SqlCommand> fillParamsAction, Action afterExecution, BDDSource source)
{
    int result;

    try
    {
        using (var connection = InitSqlConnection(source))
        using (var command = new SqlCommand(storeProcName, connection))
        {
            if (connection.State == ConnectionState.Closed)
                connection.Open();

            command.CommandType = CommandType.StoredProcedure;

            if (fillParamsAction != null)
                fillParamsAction(command);

            result = command.ExecuteNonQuery();

            if (afterExecution != null)
                afterExecution();
        }
    }
    catch (SqlException sqlExn)
    {
        Logger.Exception(string.Format("SQL CRITICAL ERROR. Stored Proc Name : {0}", storeProcName), sqlExn);
        throw;
    }
    catch (Exception exception)
    {
        Logger.Exception(string.Format("SOFTWARE CRITICAL ERROR. Stored Proc Name : {0}", storeProcName), exception);
        throw;
    }
    return result;
}

关注this link后,我尝试按照配置的时间重试请求。

我收到了以下代码:

public static int CallExecuteNonQuery(string storeProcName, Action<SqlCommand> fillParamsAction, Action afterExecution, BDDSource source)
{
    bool RetryRequest = true;
    int result = 0;

    for (int i = 0; i < Properties.Settings.Default.Request_MaximumRetry; i++)
    {
        try
        {
            if (RetryRequest)
            {
                using (var connection = InitSqlConnection(source))
                using (var command = new SqlCommand(storeProcName, connection))
                {
                    if (connection.State == ConnectionState.Closed)
                        connection.Open();

                    command.CommandType = CommandType.StoredProcedure;

                    if (fillParamsAction != null)
                        fillParamsAction(command);

                    result = command.ExecuteNonQuery();

                    if (afterExecution != null)
                        afterExecution();
                }

                RetryRequest = false;
            }
        }
        catch (SqlException sqlExn)
        {
            if (sqlExn.Errors.Cast<SqlError>().All(x => (x.Class >= 16 && x.Class < 22) || x.Class == 24))
            {
                RetryRequest = true;
                continue;
            }

            Logger.Exception(string.Format("SQL CRITICAL ERROR. Stored Proc Name : {0}", storeProcName), sqlExn);
            RetryRequest = false;
            throw;
        }
        catch (Exception exception)
        {
            Logger.Exception(string.Format("SOFTWARE CRITICAL ERROR. Stored Proc Name : {0}", storeProcName), exception);
            RetryRequest = false;
            throw;
        }
    }
    return result;
}

但我的修改并不完美。例如,在3次重试之后,代码在退出循环之前不会抛出并进入continue;部分。

3 个答案:

答案 0 :(得分:3)

为此,我创建了一个“RetryPolicy”类。

课程:

public struct RetryPolicy<T>
{
    private int mRetryMax;
    private int mRetryWaitSec;

    public RetryPolicy(int retryMax, int retryWaitSec)
    {
        mRetryMax = retryMax;
        mRetryWaitSec = retryWaitSec;
    }

    public T DoWork(System.Func<T> func)
    {
        int retries = 0;

        while (true)
        {
            try
            {
                return func();
            }
            catch when (++retries < RetryMax)
            {
                Thread.Sleep(RetryWaitSec * 1000);
            }
        }
    }

    public int RetryMax
    {
        get
        {
            return mRetryMax;
        }
    }

    public int RetryWaitSec
    {
        get
        {
            return mRetryWaitSec;
        }

        set
        {
            mRetryWaitSec = value;
        }
    }
}

使用示例:

new RetryPolicy<int>(int.MaxValue, 1000).DoWork(() =>
{
  Connect(); return 0;
});

通过这种方式,您可以使用一行客户端代码重试多次,间隔为毫秒。

您可以将其调整为通用,以便只捕获SQLException或任何您想要的内容。现在它捕获了所有例外。

它是非静态的,因此您可以在启动期间缓存RetryPolicy。

RetryPolicy policy = new RetryPolicy<int>(int.MaxValue, 1000);
// later
policy.DoWork(() => { Connect(); return 0; });

答案 1 :(得分:0)

我假设你知道自动重试的注意事项,特别是当这些涉及非幂等操作时。

我建议不要使用局部变量来跟踪成功或失败,而是建议直接使用控制流关键字:

public static int CallExecuteNonQuery(string storeProcName, Action<SqlCommand> fillParamsAction, Action afterExecution, BDDSource source)
{
    int retryCount = 0;   // recoverable exception will be rethrown 
                          // when this count reaches limit

    while (true)   // conditions for breaking out of loop inlined
    {
        try
        {
            using (var connection = InitSqlConnection(source))
            using (var command = new SqlCommand(storeProcName, connection))
            {
                if (connection.State == ConnectionState.Closed)
                    connection.Open();
                command.CommandType = CommandType.StoredProcedure;
                if (fillParamsAction != null)
                    fillParamsAction(command);
                var result = command.ExecuteNonQuery();
                if (afterExecution != null)
                    afterExecution();
                return result;   // on success, return immediately
            }
        }
        catch (SqlException sqlExn)
        {
            // if error is recoverable, and retry count has not exceeded limit,
            // then retry operation
            if (sqlExn.Errors.Cast<SqlError>().All(x => (x.Class >= 16 && x.Class < 22) || x.Class == 24)
                && ++retryCount < Properties.Settings.Default.Request_MaximumRetry)
            {
                continue;
            }

            // otherwise, rethrow exception
            Logger.Exception(string.Format("SQL CRITICAL ERROR. StoreProcName : {0}", storeProcName), sqlExn);
            throw;
        }
        catch (Exception exception)
        {
            Logger.Exception(string.Format("SOFTWARE CRITICAL ERROR. StoreProcName : {0}", storeProcName), exception);
            throw;
        }
    }
}

答案 2 :(得分:0)

您的代码看起来非常复杂,我也会利用AggregatedException类来报告所有可恢复的失败。

public static int CallExecuteNonQuery(string storeProcName, Action<SqlCommand> fillParamsAction, Action afterExecution, BDDSource source)
{
    int result = 0;
    var exceptions = new List<Exception>();

    while(true)
    {
        try
        {
            using (var connection = InitSqlConnection(source))
            using (var command = new SqlCommand(storeProcName, connection))
            {
                if (connection.State == ConnectionState.Closed)
                    connection.Open();
                command.CommandType = CommandType.StoredProcedure;
                if (fillParamsAction != null)
                    fillParamsAction(command);
                result = command.ExecuteNonQuery();
                if (afterExecution != null)
                    afterExecution();
            }
            break;
        }
        catch (SqlException sqlExn)
        {
            if (sqlExn.Errors.Cast<SqlError>().All(x => (x.Class >= 16 && x.Class < 22) || x.Class == 24))
            {
                exceptions.Add(sqlExn);
                if (exceptions.Count == Properties.Settings.Default.Request_MaximumRetry)
                {
                    throw new AggregateException("Too many attempts.", exceptions);
                }
                continue;
            }

            Logger.Exception(string.Format("SQL CRITICAL ERROR. StoreProcName : {0}", storeProcName), sqlExn);
            throw;
        }
        catch (Exception exception)
        {
            Logger.Exception(string.Format("SOFTWARE CRITICAL ERROR. StoreProcName : {0}", storeProcName), exception);
            throw;
        }
    }
    return result;
}