I had to work on an old batch job recently that needed to run against a remote database with a flakey VPN connection that kept causing ADO calls to timeout and fail at random. The technique that I came up with to let the job run all the way through was my Data Retrier of the title.
Essentially, all I needed to do was to find the calls that were failing (of which there were but a handful) and package the call up in a lambda, sending it through my new routine. That would then try to execute the call in a loop to allow it to cope with the odd failure here and there. Using reflection it is able to update the console with details about what it is trying to do, and generics let the method return the same type as the function that is passed in to it.
Function GetDataRetrier(Of T)(ByVal dataFunction _ As Func(Of T)) As T Dim currentTry As Integer = 1 Const maxTries As Integer = 3 Dim lastException As Exception = Nothing While currentTry <= maxTries ' Everything from here to the try block is just for ' outputting status, so is non-essential. Dim retType As Type = dataFunction.Method.ReturnType Dim retTypeOutput As String = retType.Name If retType.IsGenericType Then Dim genericArgs() As Type = retType.GetGenericArguments retTypeOutput += "<" For Each arg As Type In genericArgs retTypeOutput += arg.Name + ", " Next retTypeOutput += ">" End If Console.WriteLine("Trying to get data " + _ currentTry.ToString + "/" + maxTries.ToString + _ " : " + retTypeOutput) Try Dim result As T = dataFunction.Invoke() Console.WriteLine("Got data on try " + currentTry.ToString) Return result Catch ex As Exception lastException = ex End Try currentTry += 1 End While Throw lastException End Function
The code in the job that used to get the data looked something like this:
dim myData as List(Of BusinessyObject) myData = dataService.GetRelevantBusinessyObjects()
To use the retrier the second line would just have to change like so:
myData = DataRetrier(Function() _ dataService.GetRelevantBusinessyObjects())
The myData object gets the exact same content, but will now happily recall the database a couple of times if it fails. And with that change the app went from being almost impossible to run all the way through to working every time.