SQL Server  

Microsoft.Data.SqlClient Retry Logic: Building Resilient .NET SQL Server Applications

Modern .NET applications often depend on SQL Server for critical operations such as authentication, transactions, reporting, and business workflows. Even when an application and database are correctly configured, temporary failures can still occur because of network interruptions, connection pool pressure, database failovers, or transient infrastructure issues.

A retry strategy can help an application recover from these short-lived failures without immediately returning an error to the user.

The Microsoft.Data.SqlClient library provides built-in retry capabilities that can be configured for SQL Server connections and commands. This article explains how retry logic works, how to configure it in a .NET application, when it should be used, and what mistakes to avoid in production.

What Is Retry Logic in Microsoft.Data.SqlClient?

Retry logic allows Microsoft.Data.SqlClient to automatically retry certain operations when a transient SQL Server failure occurs.

Instead of following this pattern:

Application
    ↓
SQL Server
    ↓
Temporary failure
    ↓
Application returns error

a retry-enabled application can follow:

Application
    ↓
SQL Server
    ↓
Temporary failure
    ↓
Wait
    ↓
Retry
    ↓
SQL Server
    ↓
Success

This is particularly useful for cloud-hosted databases and distributed applications where temporary connectivity problems can occur.

Retry logic is not intended to hide permanent failures. For example, an invalid SQL statement, authentication failure, or missing table generally cannot be fixed by repeatedly executing the same operation.

Why Transient SQL Failures Happen

Transient failures are temporary conditions where the same operation may succeed if attempted again after a short delay.

Common examples include:

Failure scenarioRetry potentially useful?Reason
Temporary network interruptionYesConnectivity may recover
SQL Server failoverYesNew connection may become available
Temporary resource pressureYesDatabase resources may become available
Connection timeout during transient infrastructure issueSometimesA later attempt may succeed
Invalid SQL syntaxNoThe command itself is incorrect
Invalid credentialsNoRetrying does not fix authentication
Missing database/tableNoRequires configuration or code changes
Constraint violationUsually noThe application data needs correction

The important distinction is between transient and non-transient failures.

Configuring Retry Logic with Microsoft.Data.SqlClient

The retry functionality is available through Microsoft.Data.SqlClient.

A basic connection can be created like this:

using Microsoft.Data.SqlClient;

var connectionString =
    "Server=localhost;Database=SalesDb;Integrated Security=True;TrustServerCertificate=True;";

using var connection = new SqlConnection(connectionString);

await connection.OpenAsync();

To add retry behavior, configure a retry provider and assign it to the connection.

For example:

using Microsoft.Data.SqlClient;

var connectionString =
    "Server=localhost;Database=SalesDb;Integrated Security=True;TrustServerCertificate=True;";

var retryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(
    retryCount: 5,
    maxTimeInterval: TimeSpan.FromSeconds(10),
    deltaTime: TimeSpan.FromSeconds(1));

using var connection = new SqlConnection(connectionString)
{
    RetryLogicProvider = retryProvider
};

await connection.OpenAsync();

Here, the retry provider controls how the client responds when a retryable transient error occurs.

Understanding the Retry Parameters

The following values are important:

SqlConfigurableRetryFactory.CreateExponentialRetryProvider(
    retryCount: 5,
    maxTimeInterval: TimeSpan.FromSeconds(10),
    deltaTime: TimeSpan.FromSeconds(1));

retryCount specifies how many retry attempts can be made.

maxTimeInterval limits the maximum delay between retry attempts.

deltaTime controls the delay growth used by the exponential retry strategy.

The goal is to avoid immediately sending the same failed request repeatedly.

Exponential Backoff and Why It Matters

A fixed retry strategy might look like this:

Attempt 1 → Fail
Wait 1 second
Attempt 2 → Fail
Wait 1 second
Attempt 3 → Fail

This can put unnecessary pressure on an already struggling database.

Exponential backoff gradually increases the waiting period:

Attempt 1 → Fail
Wait
Attempt 2 → Fail
Wait longer
Attempt 3 → Fail
Wait even longer
Attempt 4 → Success

This gives the underlying infrastructure time to recover.

In distributed systems, exponential backoff is generally more appropriate than aggressively retrying the same request without delay.

Using Retry Logic with ASP.NET Core

In an ASP.NET Core application, it is common to centralize database configuration instead of creating connections throughout the application.

For example:

using Microsoft.Data.SqlClient;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
    builder.Configuration.GetConnectionString("DefaultConnection");

var retryProvider =
    SqlConfigurableRetryFactory.CreateExponentialRetryProvider(
        retryCount: 5,
        maxTimeInterval: TimeSpan.FromSeconds(10),
        deltaTime: TimeSpan.FromSeconds(1));

builder.Services.AddScoped<SqlConnection>(_ =>
{
    return new SqlConnection(connectionString)
    {
        RetryLogicProvider = retryProvider
    };
});

var app = builder.Build();

app.MapGet("/products", async (SqlConnection connection) =>
{
    await connection.OpenAsync();

    using var command = new SqlCommand(
        "SELECT Id, Name, Price FROM Products",
        connection);

    using var reader = await command.ExecuteReaderAsync();

    var products = new List<object>();

    while (await reader.ReadAsync())
    {
        products.Add(new
        {
            Id = reader.GetInt32(0),
            Name = reader.GetString(1),
            Price = reader.GetDecimal(2)
        });
    }

    return Results.Ok(products);
});

app.Run();

The main benefit of this approach is consistency. Database connections created through the configured dependency-injection registration receive the same retry configuration.

For larger applications, you would typically keep database access inside a repository or data-access service rather than placing SQL directly inside an endpoint.

Connection Retry vs Command Retry

One important aspect of Microsoft.Data.SqlClient retry logic is understanding what is actually being retried.

A database operation can involve:

  1. Opening a connection

  2. Executing a command

  3. Reading results

  4. Committing a transaction

Retry behavior must therefore be considered carefully, particularly for commands that modify data.

A read operation such as:

SELECT Id, Name
FROM Products
WHERE Id = @id

is generally easier to retry safely.

A write operation such as:

UPDATE Accounts
SET Balance = Balance - @amount
WHERE Id = @id

requires more consideration.

If the server processed the update but the client lost the connection before receiving the response, blindly retrying could potentially execute the business operation again.

The key issue is idempotency.

Designing Retry-Safe Database Operations

Before enabling aggressive retry behavior for write operations, determine whether repeating the operation produces the same intended result.

For example, this operation is not naturally idempotent:

UPDATE Accounts
SET Balance = Balance - 100
WHERE Id = 10;

Running it twice subtracts the amount twice.

A better design can use an operation identifier or another mechanism that allows the application to recognize an already-processed request.

For example:

INSERT INTO PaymentOperations
(
    OperationId,
    AccountId,
    Amount
)
VALUES
(
    @OperationId,
    @AccountId,
    @Amount
);

A unique constraint on OperationId can help prevent duplicate processing.

The exact implementation depends on the application's business requirements, but the principle is important:

Retrying infrastructure operations is different from safely retrying business operations.

Retry Logic Compared with Polly

.NET applications have traditionally used libraries such as Polly for resilience strategies.

Microsoft.Data.SqlClient retry logic and Polly can solve related but different problems.

CapabilityMicrosoft.Data.SqlClientPolly
SQL Server-specific retryExcellentRequires configuration
Database-aware transient handlingBuilt inApplication-defined
HTTP retryNoYes
Circuit breakerNoYes
Timeout policiesLimited to client behaviorYes
Broader application resilienceLimitedStrong
SQL-specific configurationSimpleMore custom

If your requirement is specifically SQL Server client retry behavior, the built-in Microsoft.Data.SqlClient functionality can be a straightforward choice.

If you need a broader resilience pipeline covering HTTP calls, caching, messaging, database operations, and other dependencies, an application-level resilience library may be more appropriate.

The two approaches should not be added blindly on top of each other. Layering multiple retry policies can result in unexpectedly large numbers of attempts.

Best Practices for Production

1. Keep Retry Counts Reasonable

More retries do not automatically mean better reliability.

A request that fails repeatedly can hold application resources and increase latency.

Start with conservative values and adjust them based on the application's requirements.

2. Use Exponential Backoff

Avoid immediate retry loops.

An exponential strategy gives SQL Server and the underlying infrastructure time to recover.

3. Do Not Retry Every Exception

Retry logic should only apply to failures that are actually transient.

An authentication problem will not normally be solved by five more login attempts.

4. Consider Operation Idempotency

Pay particular attention to:

  • Payments

  • Account balances

  • Inventory updates

  • Order creation

  • Message processing

  • Other state-changing operations

A retry can turn a temporary connection problem into a duplicate business operation if the design is not idempotent.

5. Monitor Retries

Retries can hide problems if they are not observable.

Track useful information such as:

  • Number of retry attempts

  • Operation duration

  • Final failure

  • Database server

  • Exception type

  • Application endpoint or operation

A system that succeeds only after multiple retries may appear healthy to users while the underlying database infrastructure is experiencing problems.

Common Mistakes

Setting an Excessive Retry Count

A configuration such as:

retryCount: 50

may keep requests alive for an unnecessarily long time.

Retry policies should be based on the application's acceptable latency and failure-recovery requirements.

Retrying Permanent Errors

Retrying invalid SQL repeatedly wastes resources.

Fix the underlying problem instead of increasing the retry count.

Adding Multiple Retry Layers

For example:

HTTP retry
    ↓
Service retry
    ↓
Repository retry
    ↓
SqlClient retry

A single failed database operation can potentially trigger many actual database requests.

Define clear ownership of retry behavior.

Ignoring Transactions

Transactions require special attention because a connection failure does not always tell the client whether the server completed the transaction.

Do not assume that an unsuccessful client response means that the database definitely rolled back the operation.

Troubleshooting Retry Behavior

When retry logic does not behave as expected, check the following:

  1. Verify that the application is using Microsoft.Data.SqlClient, not an unrelated SQL client package.

  2. Confirm that the retry provider is assigned to the connection or command configuration being used.

  3. Check the actual exception and SQL error information.

  4. Verify that the failure is transient and eligible for retry.

  5. Check application logs for repeated attempts.

  6. Review connection and command timeout settings.

  7. Check SQL Server health, networking, resource utilization, and failover events.

  8. Make sure another resilience layer is not already retrying the same operation.

Logging should make it possible to distinguish an operation that succeeded immediately from one that succeeded only after several retries.

Advantages and Disadvantages

Advantages

  • Built specifically for SQL Server client operations

  • Reduces failures caused by temporary connectivity problems

  • Supports configurable retry behavior

  • Exponential retry strategies reduce aggressive retry traffic

  • Can simplify SQL-specific resilience configuration

  • Works naturally with Microsoft.Data.SqlClient

Disadvantages

  • Does not solve permanent database failures

  • Excessive retries can increase application latency

  • Retrying write operations can introduce duplicate business operations

  • It does not replace broader application resilience patterns

  • Multiple retry layers can create unexpectedly high request counts

Conclusion

Transient database failures are a normal consideration for distributed .NET applications, particularly when applications depend on remote or cloud-hosted SQL Server environments.

Microsoft.Data.SqlClient provides a practical way to introduce SQL-aware retry behavior without implementing every retry mechanism manually. Exponential backoff, sensible retry limits, correct transient-error handling, and good observability are the key pieces of a reliable implementation.

However, retry logic should not be treated as a universal solution. The most important production consideration is understanding what is being retried and whether repeating that operation is safe.

For read-heavy workloads, carefully configured SqlClient retry logic can provide useful resilience against temporary database connectivity problems. For write-heavy or transactional workloads, combine retry configuration with idempotent application design, transaction awareness, monitoring, and an overall resilience strategy.