Wednesday, April 17, 2013

Today I ran into a problematic bug that took me awhile to identify the real problem.

I have a set of extension methods for automatic retires of Sql commands.

The extensions look like this:

Code:
namespace SqlExtensionsTest
{
    public static class SqlExtensions
    {
        public static T Retry<T>(this Func<T> command, int retryCount, ILog logger)
        {
            int count = 0;
            while (count <= retryCount)
            {
                count++;
                try
                {
                    return command();
                }
                catch (Exception ex)
                {                    
                    logger.WarnFormat("Exception in Retry operation block: retry #{0} : {1} : {2} : {3}", count, ex.Message, ex.StackTrace, ex.InnerException.StackTrace);
                    if (count == retryCount)
                    {
                        throw ex;
                    }
                }
            }
            return default(T); // this statement will never be reached, yet must be here for the method to compile
        }

        public static void RetryOpen(this SqlConnection connection, int retryCount, ILog logger)
        {
            Func<bool> func = () => { connection.Open(); return true; };
            func.Retry(retryCount, logger);
        }

        public static object RetryExecuteScalar(this SqlCommand command, int retryCount, ILog logger)
        {
            Func<object> func = () => command.ExecuteScalar();
            return func.Retry(retryCount, logger);
        }

        public static SqlDataReader RetryExecuteReader(this SqlCommand command, int retryCount, ILog logger)
        {
            Func<SqlDataReader> func = () => command.ExecuteReader();
            return func.Retry(retryCount, logger);
        }

        public static int RetryExecuteNonQuery(this SqlCommand command, int retryCount, ILog logger)
        {
            Func<int> func = () => command.ExecuteNonQuery();
            return func.Retry(retryCount, logger);
        }        
    }
}


Do you see the problem?

No?

Answer: In some cases the exception may not have an inner exception object! Ugh. It's always better to simply let the implicit ToString method do it's job. We can rewrite the log entry like so:


logger.WarnFormat("Exception in Retry operation block: retry #{0} : {1}", count, ex);
                    if (count == retryCount)
                        throw;

Not only does that work all the time, it's easier to read as well. As a bonus, we rethrow the original exception withou the "ex" (the original has the side effect of losing the original stack info).