There are many articles describing how to make a retry policy when using Dapper. I especially like this extension method implementation. But you cannot inject any class into extension methods because extension methods reside in a static class. Therefore you don’t have a constructor where the injection can happen.
Instead, another easy solution is to create a base class for your Dapper repository classes.
STEP 1: THE PREREQUISITES
You need Dapper and Polly of course:
Then you need a reference to a class called “SqlServerTransientExceptionDetector“. You can either grab the code and paste in into your class, or you can reference Microsoft.EntityFrameworkCore.SqlServer NuGet:
- SqlServerTransientExceptionDetector raw code to be copied into your solution. Check your license model before copying the code.
- Microsoft.EntityFrameworkCore.SqlServer NuGet package
STEP 2: THE BASE CLASS
I’m assuming you use standard dependency injection, and that you have an ILogger to be used.
using Microsoft.Extensions.Logging;
using Polly.Retry;
using Polly;
using System.ComponentModel;
using System.Data.SqlClient;
using System.Data;
using Dapper;
namespace MyCode
{
public class DapperRetryPolicyBaseClass<T>
{
private readonly ILogger<T> _logger;
private static readonly IEnumerable<TimeSpan> _retryTimes = new[]
{
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(4),
TimeSpan.FromSeconds(8)
};
private readonly AsyncRetryPolicy _asyncRetryPolicy;
public DapperRetryPolicyBaseClass(ILogger<T> logger)
{
_logger = logger;
_asyncRetryPolicy = Policy
.Handle<SqlException>(SqlServerTransientExceptionDetector.ShouldRetryOn)
.Or<TimeoutException>()
.OrInner<Win32Exception>(SqlServerTransientExceptionDetector.ShouldRetryOn)
.WaitAndRetryAsync(_retryTimes,
(exception, timeSpan, retryCount, context) =>
{
_logger.LogWarning(exception, "{InstanceType} SQL Exception. retry #{RetryCount}. Exception {Exception}", _logger.GetType(), retryCount, exception);
});
}
protected async Task<int> ExecuteAsyncWithRetry(IDbConnection connection,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = null,
CommandType? commandType = null
) =>
await _asyncRetryPolicy.ExecuteAsync(async () => await connection.ExecuteAsync(sql, param, transaction, commandTimeout, commandType));
protected async Task<IEnumerable<T>> QueryAsyncWithRetry<T>(IDbConnection connection,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = null,
CommandType? commandType = null
) =>
await _asyncRetryPolicy.ExecuteAsync(async () => await connection.QueryAsync<T>(sql, param, transaction, commandTimeout, commandType));
}
}
Being a base class, we suddenly have a constructor where the injection can happen. Also, the Polly retry policy can be defined in the constructor where the ILogger is available.
STEP 3: HOW TO USE THE BASE CLASS
using System.Data.SqlClient;
using Dapper;
using Microsoft.Extensions.Logging;
namespace MyCode
{
public class MyRepository : DapperRetryPolicyBaseClass<MyRepository>
{
private readonly ILogger<JsonEventRepository> _logger;
public JsonEventRepository(ILogger<MyRepository> logger) : base(logger)
{
_logger = logger;
}
public async Task Execute(string someDateToBeInsertedIntoATable)
{
using var sqlConnection = new SqlConnection("some-connection-string");
await base.ExecuteWithRetryAsync(sqlConnection, "insert into some_table (field) values (@field)",
new
{
Field = someDateToBeInsertedIntoATable,
}
);
}
}
}
Inherit from DapperRetryPolicyBaseClass and define the class type, and the ILogger of the base class will have the same type as the MyRepository.
Then use the base retry methods. On any retry, the base class will log the retry using the ILogger.
That’s it. You are now a Dapper expert. Happy coding.
MORE TO READ:
- Dapper Retry With Polly from Williams Garage
- SqlServerTransientExceptionDetector as seen in the .NET API Catalog
- SqlServerTransientExceptionDetector raw file
- How to do retries in EF Core by Maclain Wiltzer
- SQL Server Retries with Dapper and Polly by Ben Hyrman
- Extension methods for calling Dapper asynchronously with a Polly retry from GitHub
- C# Using Dapper as your SQL framework in .NET Core by briancaos