Automatic Retry for LINQ to SQL

A new library that makes retrying transient failures in LINQ to SQL easier.

Published on Tuesday, December 23, 2014

GitHub

NuGet

LinqToSqlRetry is a simple library to help manage retries in LINQ to SQL. This is particularly important in cloud-based infrastructures like Azure where transient failures are not uncommon. And despite the popularity of Entity Framework, Dapper, and other ORM or data access libraries, there is still a place for simple LINQ to SQL code.

Retry logic is provided via extension methods, so you will need to bring the LinqToSqlRetry namespace into scope in every file you need retry logic:

using LinqtoSqlRetry;

Retry On Submit Changes

Instead of using DataContext.SubmitChanges() just use DataContext.SubmitChangesRetry():

using(var context = new MyDbContext())
{
  context.Items.InsertOnSubmit(new Item { Name = "ABC" });
  context.SubmitChangesRetry();
}

Retry on Queries

Add the Retry() extension method to the end of your queries. It should generally be the last extension you use before materializing the query (which happens when you use extensions like ToList() or Count()):

using(var context = new MyDbContext())
{
  int count = context.Items.Where(x => x.Name == "ABC").Retry().Count();
}

Retry Policy

The retry logic is controlled by a policy that indicates when a retry should take place and how long to wait before retrying the operation. Two policies are supplied:

  • LinearRetry retries a specific number of times (3 by default) and waits a specified amount each time (10 seconds by default).
  • ExponentialRetry retries a specific number of times (3 by default) and waits an increasing multiple of time (5 seconds by default) after an initial wait on the first retry (10 seconds by default).

By default the LinearRetry policy is used. The policy that you use and it's settings can be changed by passing it in to any of the extension methods:

using(var context = new MyDbContext())
{
  // Start with a 4 second wait, increasing by a factor of 2, for 5 attempts
  var retryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(2), 5);
  context.Items.InsertOnSubmit(new Item { Name = "ABC" });
  context.SubmitChangesRetry(retryPolicy);
  int count = context.Items.Where(x => x.Name == "ABC").Retry(retryPolicy).Count();
}

You can specify your own custom policies to do things like log retry attempts, use more complex logic, retry on different types of errors, etc. by implementing IRetryPolicy.

Retry for Arbitrary Operations

You can also retry any arbitrary operation with the Retry() extension methods on any IRetryPolicy object. There are two of them, one takes an Action and the other takes a Func<t> and returns the result. For example:

var retryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(2), 5);
retryPolicy.Retry(() => AMethodThatMightFail());

Under the Hood

The retry logic under the hood is fairly simple:

int retryCount = 0;
while (true)
{
    try
    {
        return func();
    }
    catch (Exception ex)
    {
        TimeSpan? interval = retryPolicy.ShouldRetry(retryCount, ex);
        if (!interval.HasValue)
        {
            throw;
        }
        Thread.Sleep(interval.Value);
    }
    retryCount++;
}

In the code above, the function in the call to func() and the retryPolicy object are provided based on usage. This just gives you an idea what's going on during the retry loop. Just look in the GitHub repository for more information.

comments powered by Disqus