PollyEFCore 1.0.1
dotnet add package PollyEFCore --version 1.0.1
NuGet\Install-Package PollyEFCore -Version 1.0.1
<PackageReference Include="PollyEFCore" Version="1.0.1" />
<PackageVersion Include="PollyEFCore" Version="1.0.1" />
<PackageReference Include="PollyEFCore" />
paket add PollyEFCore --version 1.0.1
#r "nuget: PollyEFCore, 1.0.1"
#:package PollyEFCore@1.0.1
#addin nuget:?package=PollyEFCore&version=1.0.1
#tool nuget:?package=PollyEFCore&version=1.0.1
PollyEFCore
Polly v8 resilience pipelines for Entity Framework Core — retry, timeout and circuit-breaker for every EF Core query and SaveChanges, for any database provider, with a single line of registration.
services.AddDbContext<AppDbContext>(options =>
options
.UseSqlServer(connectionString)
.AddPollyResilience(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
})));
Every query and SaveChangesAsync() is now automatically retried on transient failure — no changes to your DbContext, repositories, or handlers.
Why PollyEFCore?
EF Core's built-in EnableRetryOnFailure() only handles SQL Azure connection failures. PollyEFCore gives you the full power of Polly v8 for any EF Core provider.
| Feature | EnableRetryOnFailure() |
PollyEFCore |
|---|---|---|
| Provider support | SQL Server / Azure SQL only | Any (Postgres, MySQL, SQLite, CosmosDB…) |
| Retry strategy | Fixed with jitter, hardcoded delays | Exponential, linear, constant, custom |
| Timeout per command | ❌ | ✅ |
| Circuit breaker | ❌ | ✅ stop hammering a broken DB |
| Hedging (speculative queries) | ❌ | ✅ |
| Exception filter | Hardcoded SQL error codes | Any predicate — your codes, your rules |
| Observability | ❌ | ✅ via PollyOpenTelemetry |
Installation
dotnet add package PollyEFCore
Targets net8.0 and net9.0 (requires EF Core 8+).
Dependencies: Polly.Core 8.*, Microsoft.EntityFrameworkCore.Relational 8.*
Quick start
Automatic interception (recommended)
One call on DbContextOptionsBuilder — all commands wrapped automatically, no handler changes needed:
services.AddDbContext<AppDbContext>(options =>
options
.UseNpgsql(connectionString) // any provider
.AddPollyResilience(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(100),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
})
.AddTimeout(TimeSpan.FromSeconds(30))));
Use your DbContext exactly as normal — zero code changes required:
// Repository — completely unchanged
public async Task<List<Product>> GetProductsAsync(CancellationToken ct)
=> await _context.Products.Where(p => p.Active).ToListAsync(ct); // retried automatically
public async Task SaveAsync(Product product, CancellationToken ct)
{
_context.Products.Add(product);
await _context.SaveChangesAsync(ct); // retried automatically
}
Explicit wrapping (per-operation control)
Use ExecuteWithResilienceAsync when you need a different pipeline per operation, or when working inside an explicit transaction:
var products = await _context.Database.ExecuteWithResilienceAsync(
_pipeline,
ct => _context.Products.Where(p => p.Active).ToListAsync(ct),
cancellationToken);
// Void overload
await _context.Database.ExecuteWithResilienceAsync(
_pipeline,
async ct =>
{
_context.Orders.Add(order);
await _context.SaveChangesAsync(ct);
},
cancellationToken);
Provider examples
SQL Server — transient error filtering
Filter to known SQL Server transient error codes instead of catching all exceptions:
// Known SQL Server transient error numbers
private static readonly HashSet<int> SqlTransientErrors = new()
{
-2, // Timeout
20, // General network error
64, // Connection to SQL lost
233, // No process at the other end of the pipe
10053, // Transport-level error
10054, // Remote host forcibly closed
10060, // Connection attempt failed
40197, // Service encountered an error processing your request
40501, // Service is currently busy
40613, // Database is not currently available (Azure SQL)
49918, // Cannot process request — not enough resources
};
services.AddDbContext<AppDbContext>(options =>
options
.UseSqlServer(connectionString)
.AddPollyResilience(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder()
.Handle<SqlException>(ex => SqlTransientErrors.Contains(ex.Number))
.Handle<TimeoutException>(),
})));
PostgreSQL (Npgsql)
services.AddDbContext<AppDbContext>(options =>
options
.UseNpgsql(connectionString)
.AddPollyResilience(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(100),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder()
.Handle<NpgsqlException>(ex => ex.IsTransient)
.Handle<TimeoutException>(),
})));
MySQL (Pomelo)
services.AddDbContext<AppDbContext>(options =>
options
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.AddPollyResilience(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
ShouldHandle = new PredicateBuilder()
.Handle<MySqlException>(ex => ex.IsTransient)
.Handle<TimeoutException>(),
})));
ASP.NET Core example with circuit-breaker
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ShopDbContext>(options =>
options
.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
.AddPollyResilience(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
})
.AddTimeout(TimeSpan.FromSeconds(30))
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 10,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(15),
})));
Full-stack CQRS: PollyMediatR + PollyEFCore
Combine with PollyMediatR for end-to-end resilience in CQRS/MediatR apps — the MediatR layer retries the whole handler, and the EF Core layer retries individual commands:
// MediatR handler layer (outer) — retries the whole handler on failure
services.AddPollyMediatR(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 2,
ShouldHandle = new PredicateBuilder().Handle<TransientException>(),
}));
// EF Core layer (inner) — retries individual SQL commands on transient DB errors
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(cs).AddPollyResilience(pipeline =>
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = new PredicateBuilder().Handle<SqlException>(IsSqlTransient),
})));
⚠️ Transaction note
The automatic interceptor wraps individual ADO.NET commands. When using explicit DbTransaction objects, configure your pipeline to handle only connection-level failures, or use ExecuteWithResilienceAsync for full unit-of-work retry control.
Related packages
| Package | Downloads | Description |
|---|---|---|
| PollyMediatR | Polly v8 pipelines for MediatR request handlers | |
| PollyBackoff | Jitter, linear & custom backoff for Polly v8 retry | |
| PollyChaos | Fault & latency injection (Simmy for Polly v8) | |
| PollyCaching | Cache-aside resilience strategy for Polly v8 | |
| PollyBulkhead | Bulkhead / concurrency limiter for Polly v8 | |
| PollyOpenTelemetry | OpenTelemetry metrics & tracing for Polly v8 |
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.28)
- Polly.Core (>= 8.7.0)
-
net9.0
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.28)
- Polly.Core (>= 8.7.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.0.1: Improved NuGet metadata and README with SQL Server transient error filtering, Npgsql/PostgreSQL examples, and PollyMediatR+PollyEFCore full-stack CQRS example.
1.0.0: Initial release. ResilienceDbCommandInterceptor wraps EF Core queries and SaveChanges with a Polly v8 ResiliencePipeline. AddPollyResilience() DI extension for DbContextOptionsBuilder. DatabaseFacade.ExecuteWithResilienceAsync() for explicit operation wrapping.