捕获SQLException并重试的好C#编码风格是什么

捕获SQLException并重试的好C#编码风格是什么,c#,sql,linq,C#,Sql,Linq,我有一个方法,可以调用SQLServer函数对表执行自由文本搜索。该函数在第一次调用时偶尔会导致SQLException:“全文查询字符串的分词超时”。因此,我通常希望重试该请求,因为它将在后续请求中成功。构建重试逻辑的好风格是什么。目前,我有以下几点: var retryCount = 0; var results = new List<UserSummaryDto>(); using (var ctx = new UsersDataContext(ConfigurationMan

我有一个方法,可以调用SQLServer函数对表执行自由文本搜索。该函数在第一次调用时偶尔会导致SQLException:“全文查询字符串的分词超时”。因此,我通常希望重试该请求,因为它将在后续请求中成功。构建重试逻辑的好风格是什么。目前,我有以下几点:

var retryCount = 0;
var results = new List<UserSummaryDto>();
using (var ctx = new UsersDataContext(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString))
{
    for (; ; )
    {
        try
        {
            results = ctx.SearchPhoneList(value, maxRows)
                         .Select(user => user.ToDto())
                         .ToList();
            break;
        }
        catch (SqlException)
        {
            retryCount++;
            if (retryCount > MAX_RETRY) throw;
        }
    }
}

return results;
var-retryCount=0;
var results=新列表();
使用(var ctx=new UsersDataContext(ConfigurationManager.ConnectionString[CONNECTION\u STRING\u KEY].ConnectionString))
{
对于(;;)
{
尝试
{
结果=ctx.SearchPhoneList(值,maxRows)
.Select(user=>user.ToDto())
.ToList();
打破
}
捕获(SqlException)
{
retryCount++;
如果(重试次数>最大重试次数)抛出;
}
}
}
返回结果;

我会将异常处理更改为仅在某些错误上重试:

  • 12041205死锁
  • -2超时
  • -1连接断开
这些是基本的“可重试”错误

编辑,我完全忘记了等待,所以您不必敲打SQL框:

  • 添加500毫秒的死锁等待
  • 在超时时添加5秒延迟
编辑2:

我是一名开发DBA,不太会做C。
我的答案是纠正调用的异常处理…

我认为用一个方面指定重试次数的方法注释一个方法会导致更结构化的代码,尽管它需要一些基础结构编码。

这不是一种好的风格,但有时你必须这样做,因为你根本无法改变现有的代码,必须处理它

对于这个场景,我使用以下通用方法。请注意该方法,它有时在重新抛出场景中非常有用

public static void RetryBeforeThrow<T>(Action action, int retries, int timeout) where T : Exception
{
    if (action == null)
        throw new ArgumentNullException("action", string.Format("Argument '{0}' cannot be null.", "action"));

    int tries = 1;

    do
    {
        try
        {
            action();
            return;
        }
        catch (T ex)
        {
            if (retries <= 0)
            {
                PreserveStackTrace(ex);
                throw;
            }

            Thread.Sleep(timeout);
        }
    }
    while (tries++ < retries);
}

/// <summary>
/// Sets a flag on an <see cref="T:System.Exception"/> so that all the stack trace information is preserved 
/// when the exception is re-thrown.
/// </summary>
/// <remarks>This is useful because "throw" removes information, such as the original stack frame.</remarks>
/// <see href="http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx"/>
public static void PreserveStackTrace(Exception ex)
{
    MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
    preserveStackTrace.Invoke(ex, null);
}
publicstaticvoidretrybeforetrow(Action-Action,int-retries,int-timeout),其中T:Exception
{
if(action==null)
抛出新的ArgumentNullException(“action”,string.Format(“参数“{0}”不能为null。”,“action”);
int=1;
做
{
尝试
{
动作();
返回;
}
渔获物(T-ex)
{
if(retries MethodWhichFails(),3100);

这样做没有好的风格。你最好弄清楚为什么请求第一次失败,第二次成功

Sql Server似乎必须首先编译执行计划,然后执行查询。因此,第一次调用失败,因为合并的时间超过了timeout属性,第二次调用成功,因为执行计划已编译并保存

我不知道UsersDataContext是如何工作的,但可能在实际执行查询之前,您可以选择
准备查询

真实答案:如果我必须这样做,我只会重试一次,不会重试,如下所示:

var results = new List<UserSummaryDto>();
using (var ctx = new 
    UsersDataContext(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString))
{
        try
        {
            results = ctx.SearchPhoneList(value, maxRows)
                         .Select(user => user.ToDto())
                         .ToList();
            break;
        }
        catch (SqlException)
        {
            try
            {
                results = ctx.SearchPhoneList(value, maxRows)
                         .Select(user => user.ToDto())
                         .ToList();
                break;
            }
            catch (SqlException)
            {
                // set return value, or indicate failure to user however
            }
        }
    }
}

return results;
SqlConnectionBroken = -1,
SqlTimeout = -2,
SqlOutOfMemory = 701,
SqlOutOfLocks = 1204,
SqlDeadlockVictim = 1205,
SqlLockRequestTimeout = 1222,
SqlTimeoutWaitingForMemoryResource = 8645,
SqlLowMemoryCondition = 8651,
SqlWordbreakerTimeout = 30053
var results=newlist();
使用(var ctx=new)
UsersDataContext(ConfigurationManager.ConnectionString[CONNECTION\u STRING\u KEY].ConnectionString))
{
尝试
{
结果=ctx.SearchPhoneList(值,maxRows)
.Select(user=>user.ToDto())
.ToList();
打破
}
捕获(SqlException)
{
尝试
{
结果=ctx.SearchPhoneList(值,maxRows)
.Select(user=>user.ToDto())
.ToList();
打破
}
捕获(SqlException)
{
//设置返回值,或者向用户指示失败
}
}
}
}
返回结果;

虽然我相信您不会滥用重试过程,但您可能会诱使您的继任者增加重试次数,作为一种快速修复方法。

将相关代码提取到其自己的方法中,然后使用递归

伪代码:

try
{
    doDatabaseCall();
}
catch (exception e)
{
    //Check exception object to confirm its the error you've been experiencing as opposed to the server being offline.
    doDatabaseCall();
}

感谢所有的反馈。我自己回答这个问题,这样我就可以从给出的答案中加入元素。如果我遗漏了什么,请告诉我。我的方法是:

var results = new List<UserSummaryDto>();
Retry<UsersDataContext>(ctx => results = ctx.SearchPhoneList(value, maxRows)
                                            .Select(user => user.ToDto())
                                            .ToList());
return results;
var results=newlist();
重试(ctx=>results=ctx.SearchPhoneList(值,maxRows)
.Select(user=>user.ToDto())
.ToList());
返回结果;
我已经重构了原始方法以供重用。仍然有很多嵌套级别。它还依赖于数据上下文有一个默认构造函数,这可能太严格了。@Martin,我考虑过包括你的PreserveStackTrace方法,但在这种情况下,我认为它没有真正增加足够的价值-很高兴知道,以供将来参考,不是吗汉克斯:

private const int MAX_RETRY = 2;
private const double LONG_WAIT_SECONDS = 5;
private const double SHORT_WAIT_SECONDS = 0.5;
private static readonly TimeSpan longWait = TimeSpan.FromSeconds(LONG_WAIT_SECONDS);
private static readonly TimeSpan shortWait = TimeSpan.FromSeconds(SHORT_WAIT_SECONDS);
private enum RetryableSqlErrors
{
    Timeout = -2,
    NoLock = 1204,
    Deadlock = 1205,
    WordbreakerTimeout = 30053,
}

private void Retry<T>(Action<T> retryAction) where T : DataContext, new()
{
    var retryCount = 0;
    using (var ctx = new T())
    {
        for (;;)
        {
            try
            {
                retryAction(ctx);
                break;
            }
            catch (SqlException ex)
                when (ex.Number == (int) RetryableSqlErrors.Timeout &&
                      retryCount < MAX_RETRY)
            {
                Thread.Sleep(longWait);
            }
            catch (SqlException ex)
                when (Enum.IsDefined(typeof(RetryableSqlErrors), ex.Number) &&
                      retryCount < MAX_RETRY)
            {
                Thread.Sleep(shortWait);
            }
            retryCount++;
        }
    }
}
private const int MAX_RETRY=2;
私人常数双长等待秒=5;
私人常数双短等待秒=0.5;
私有静态只读TimeSpan longWait=TimeSpan.FromSeconds(长等待秒);
私有静态只读TimeSpan shortWait=TimeSpan.FromSeconds(SHORT_WAIT_SECONDS);
私有枚举RetryableSqlErrors
{
超时=-2,
NoLock=1204,
死锁=1205,
WordbreakerTimeout=30053,
}
private void Retry(Action retryAction),其中T:DataContext,new()
{
var-retryCount=0;
使用(var ctx=new T())
{
对于(;;)
{
尝试
{
再反应(ctx);
打破
}
catch(SqlException-ex)
当(ex.Number==(int)RetryableSqlErrors.Timeout&&
retryCount<最大重试次数)
{
线程。睡眠(长等待);
}
catch(SqlException-ex)
当(Enum.IsDefined)(typeof(RetryableSqlErrors),例如Number)&&
retryCount<最大重试次数)
private const int MAX_RETRY = 2;
private const double LONG_WAIT_SECONDS = 5;
private const double SHORT_WAIT_SECONDS = 0.5;
private static readonly TimeSpan longWait = TimeSpan.FromSeconds(LONG_WAIT_SECONDS);
private static readonly TimeSpan shortWait = TimeSpan.FromSeconds(SHORT_WAIT_SECONDS);
private enum RetryableSqlErrors
{
    Timeout = -2,
    NoLock = 1204,
    Deadlock = 1205,
    WordbreakerTimeout = 30053,
}

private void Retry<T>(Action<T> retryAction) where T : DataContext, new()
{
    var retryCount = 0;
    using (var ctx = new T())
    {
        for (;;)
        {
            try
            {
                retryAction(ctx);
                break;
            }
            catch (SqlException ex)
                when (ex.Number == (int) RetryableSqlErrors.Timeout &&
                      retryCount < MAX_RETRY)
            {
                Thread.Sleep(longWait);
            }
            catch (SqlException ex)
                when (Enum.IsDefined(typeof(RetryableSqlErrors), ex.Number) &&
                      retryCount < MAX_RETRY)
            {
                Thread.Sleep(shortWait);
            }
            retryCount++;
        }
    }
}
SqlConnectionBroken = -1,
SqlTimeout = -2,
SqlOutOfMemory = 701,
SqlOutOfLocks = 1204,
SqlDeadlockVictim = 1205,
SqlLockRequestTimeout = 1222,
SqlTimeoutWaitingForMemoryResource = 8645,
SqlLowMemoryCondition = 8651,
SqlWordbreakerTimeout = 30053