Polly v8 resilience for SQL Server and Azure SQL — retry, timeout, and circuit-breaker for SqlConnection queries and commands, plus a built-in SqlServerTransientErrors predicate covering all common SQL Server and Azure SQL transient error numbers. Zero changes to your existing SQL.
// Before
await cmd.ExecuteNonQueryAsync();
// After — automatic retry + timeout on every operation
var resilient = connection.WithPolly(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = SqlServerTransientErrors.IsTransient, // built-in ✔
})
.AddTimeout(TimeSpan.FromSeconds(30)));
await resilient.ExecuteAsync("INSERT INTO Orders (CustomerId) VALUES (@id)",
parameters: [new SqlParameter("@id", customerId)]);dotnet add package PollySqlClientTargets net6.0, net8.0, and net9.0.
Dependencies: Polly.Core 8.*, Microsoft.Data.SqlClient 6.*, Microsoft.Extensions.DependencyInjection.Abstractions 8.*
Knowing which SQL Server errors are safe to retry is the hard part. PollySqlClient ships a pre-built SqlServerTransientErrors.IsTransient predicate so you don't have to look up error numbers.
new RetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = SqlServerTransientErrors.IsTransient,
}| Error | Description |
|---|---|
1205 |
Deadlock victim |
1204 |
Instance ran out of locks |
233 |
Connection does not exist (named pipes) |
64 |
Connection lost during login |
10053 |
Transport-level error receiving from server |
10054 |
Transport-level error sending to server |
10060 |
Network error / server not found |
| Error | Description |
|---|---|
40613 |
Database not currently available — retry |
40501 |
Service busy — retry after 10 seconds |
40197 |
Error processing request — retry |
49920 |
Too many operations in progress |
49919 |
Too many create/update operations |
49918 |
Not enough resources to process request |
10929 |
Resource limit reached — retry later |
10928 |
Resource limit hit |
4221 |
Login to read-secondary failed |
The raw set is also available for extension:
var myErrors = SqlServerTransientErrors.ErrorNumbers.ToHashSet();
myErrors.Add(4060); // Cannot open database (sometimes transient)
new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<SqlException>(ex =>
ex.Errors.Cast<SqlError>().Any(e => myErrors.Contains(e.Number)))
}using PollySqlClient;
await using var connection = new SqlConnection(connectionString);
var resilient = connection.WithPolly(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = SqlServerTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(30)));
await resilient.OpenAsync();
// Non-query
await resilient.ExecuteAsync(
"INSERT INTO Events (Type, Payload) VALUES (@type, @payload)",
parameters: [new("@type", "OrderPlaced"), new("@payload", json)]);
// Query with mapper
var orders = await resilient.QueryAsync(
"SELECT Id, Total FROM Orders WHERE CustomerId = @id",
reader => new Order(reader.GetInt32(0), reader.GetDecimal(1)),
parameters: [new SqlParameter("@id", customerId)]);
// Scalar
var count = await resilient.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM Orders");// Program.cs
builder.Services.AddScoped(_ => new SqlConnection(connectionString));
builder.Services.AddPollySqlClient(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = SqlServerTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(30))
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 10,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(15),
}));
// Repository
public class OrderRepository(SqlConnection db, ResiliencePipeline pipeline)
{
public async Task<List<Order>> GetByCustomerAsync(int customerId)
{
var resilient = db.WithPolly(pipeline);
await resilient.OpenAsync();
return await resilient.QueryAsync(
"SELECT Id, Total FROM Orders WHERE CustomerId = @id",
r => new Order(r.GetInt32(0), r.GetDecimal(1)),
parameters: [new SqlParameter("@id", customerId)]);
}
}| Method | Description |
|---|---|
OpenAsync |
Open the connection with retry |
ExecuteAsync |
Execute non-query, returns rows affected |
ExecuteScalarAsync<T> |
Execute scalar, returns first column of first row |
QueryAsync<T> |
Query with row mapper, returns List<T> |
QueryFirstOrDefaultAsync<T> |
Query with row mapper, returns first row or default |
[Timeout] → [Retry] → [Circuit Breaker] → [SqlClient]
pipeline
.AddTimeout(TimeSpan.FromSeconds(30)) // 1. Overall deadline
.AddRetry(retryOptions) // 2. Retry transient failures
.AddCircuitBreaker(cbOptions) // 3. Open circuit under load| Package | Downloads | Description |
|---|---|---|
| PollyHealthChecks | ASP.NET Core health checks for Polly v8 circuit breakers — expose circuit-breaker state (Closed, HalfOpen, Open, Isolated) as /health endpoint responses | |
| PollyBackoff | Backoff delay strategies for Polly v8 resilience pipelines | |
| PollyEFCore | Polly v8 resilience pipelines for Entity Framework Core — wrap every EF Core query and SaveChanges with retry, timeout and circuit-breaker via a single AddPollyResilience() call | |
| PollyMailKit | Polly v8 resilience pipelines for MailKit — retry, timeout, and circuit-breaker for SmtpClient.SendAsync and any MailKit SMTP operation | |
| PollyMassTransit | Polly v8 resilience pipelines for MassTransit — retry, timeout, and circuit-breaker for IBus.Publish and ISendEndpointProvider.Send | |
| PollyNpgsql | Polly v8 resilience pipelines for Npgsql (PostgreSQL) — retry, timeout, and circuit-breaker for NpgsqlConnection queries and commands, plus a built-in PostgresTransientErrors predicate covering all common PostgreSQL transient SQLSTATE codes | |
| PollyOpenAI | Polly v8 resilience for OpenAI and Azure OpenAI API calls | |
| PollyAzureEventHub | Polly v8 resilience pipelines for Azure Event Hubs — retry, timeout, and circuit-breaker for EventHubProducerClient and EventHubConsumerClient | |
| PollyElasticsearch | Polly v8 resilience pipelines for Elastic.Clients.Elasticsearch 8+ — retry, timeout, and circuit-breaker for any Elasticsearch operation, plus a built-in ElasticTransientErrors predicate covering rate limiting (429), service unavailability (503), gateway timeouts (504), and connection failures | |
| PollyHangfire | Polly v8 resilience pipelines for Hangfire — retry, timeout, and circuit-breaker for IBackgroundJobClient.Enqueue and Schedule | |
| PollySendGrid | Polly v8 resilience pipelines for SendGrid — retry, timeout, and circuit-breaker for ISendGridClient.SendEmailAsync | |
| PollyMongo | Polly v8 resilience pipelines for MongoDB.Driver — wrap Find, InsertOne, UpdateOne, DeleteOne and other IMongoCollection calls with retry, timeout, circuit-breaker, and more using a single ResilientMongoCollection decorator | |
| PollyDapper | Polly v8 resilience pipelines for Dapper — wrap QueryAsync, ExecuteAsync, and other Dapper calls with retry, timeout, circuit-breaker, and more using a single ResilientDbConnection decorator | |
| PollyMediatR | Polly v8 resilience pipelines for MediatR — add retry, timeout, circuit-breaker, rate-limiting, hedging, and chaos engineering to any MediatR request handler with a single line of DI registration | |
| PollyAzureKeyVault | Polly v8 resilience pipelines for Azure Key Vault — retry, timeout, and circuit-breaker for SecretClient, KeyClient, and CertificateClient | |
| PollyAzureQueueStorage | Polly v8 resilience pipelines for Azure Queue Storage — retry, timeout, and circuit-breaker for Azure.Storage.Queues QueueClient | |
| PollyRedis | Polly v8 resilience for StackExchange.Redis | |
| PollyAzureServiceBus | Polly v8 resilience for Azure Service Bus — retry, circuit breaker, and timeout for sending and receiving messages | |
| PollyAzureBlob | Polly v8 resilience pipelines for Azure Blob Storage — wrap BlobClient and BlobContainerClient operations with retry, timeout, circuit-breaker, and more using ResilientBlobClient and ResilientBlobContainerClient decorators | |
| PollyAzureTableStorage | Polly v8 resilience pipelines for Azure Table Storage — retry, timeout, and circuit-breaker for Azure.Data.Tables TableClient |
The author of this package is available for consulting on Polly v8 resilience, Azure cloud architecture, and clean .NET design.
→ solidqualitysolutions.com · LinkedIn
MIT