Joydip Kanjilal
Contributor

How to implement database connection resiliency in ASP.NET Core

how-to
12 Apr 20246 mins
C#Development Libraries and FrameworksMicrosoft .NET

Take advantage of connection resiliency in EF Core to detect errors and retry commands and enable your ASP.NET Core application to overcome transient faults.

resilient resilience weed growing in desert by barcin via getty images
Credit: Barcin / Getty Images

A high-quality application must be stable, performant, scalable, and reliable. The reliability of an application depends on many factors, but one of the most important is resiliency, or the ability to withstand failures or faults during run time. In this article, we’ll see how we can bring resiliency to database connections in ASP.NET Core using Entity Framework Core.

EF Core has a feature called connection resiliency that automatically retries failed database commands to maintain connectivity during transient errors or network instability. By encapsulating the logic for identifying failures and retrying commands, this feature allows us to devise execution plans for different database failure situations.

To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.

Create an ASP.NET Core Web API project in Visual Studio 2022

To create an ASP.NET Core Web API project in Visual Studio 2022, follow the steps outlined below.

  1. Launch the Visual Studio 2022 IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  6. Click Next.
  7. In the “Additional Information” window, select “.NET 8.0 (Long Term Support)” as the framework version and ensure that the “Use controllers” box is checked. We will be using controllers in this project.
  8. Elsewhere in the “Additional Information” window, leave the “Authentication Type” set to “None” (the default) and ensure the check boxes “Enable Open API Support,” “Configure for HTTPS,” and “Enable Docker” remain unchecked. We won’t be using any of those features here.
  9. Click Create.

We’ll use this ASP.NET Core Web API project to work with the code examples in the sections below.

Create an execution strategy in EF Core

In EF Core, an execution strategy is defined as a component that encapsulates the logic for handling database command execution errors and retrying them if the errors are deemed transitory. An execution strategy allows developers to make sure their applications can gracefully recover from transient errors without requiring human intervention.

You can create an execution strategy using the CreateExecutionStrategy method as shown in the code snippet given below.

var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
   await using var transaction = await _context.Database.BeginTransactionAsync();
   //Write your custom code here to perform CRUD operations
   //against the database
   await transaction.CommitAsync();
});

As you can see from the following code example, an execution strategy is usually specified in the OnConfiguring method of your custom DbContext class.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server= mssqldb;Database=Test;Trusted_Connection=True",
            options => options.EnableRetryOnFailure());
}

If you’re using Azure SQL Database, EF Core already provides the resiliency and retry logic for your database. However, you must enable the EF Core execution strategy for each DbContext connection your application makes if you want to leverage EF Core connection resiliency. The following code snippet illustrates how you can enable resilient SQL connections using EF Core that are retried whenever the database connection goes down.

builder.Services.AddDbContext<LibraryContext>(options =>
    {
        options.UseSqlServer(builder.Configuration["IDGConnectionString"],
        sqlServerOptionsAction: sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 5,
            maxRetryDelay: TimeSpan.FromSeconds(45));
        });
    });

Use connection resiliency with transactions in EF Core

If you’ve enabled retries in EF Core, every call to the SaveChanges method will be retried as a unit if a database connection failure occurse. However, if you execute a transaction block in your application’s code, using the BeginTransaction method, you must invoke an execution strategy explicitly using a delegate to ensure that every operation inside the transaction is executed. This is shown in the code snippet given below.

var strategy = db.Database.CreateExecutionStrategy();
strategy.Execute(
    () =>
    {
        using var context = new LibraryContext();
        using var transaction = context.Database.BeginTransaction();
        context.Books.Add(new Book { Id = 1, Title = "Let us C" });
        context.SaveChanges();
        context.Books.Add(new Book { Id = 2, Title = "Mastering C# 8.0" });
        context.SaveChanges();
        transaction.Commit();
    });

Handle database connection failures in ASP.NET Core

When working with database connections using EF Core, you should handle potential connection failures by catching exceptions and retrying the database operations. Consider the following entity class called Customer.

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string PostalCode { get; set; } = string.Empty;
    public string Country { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
}

The following code listing illustrates the DbConnectService class that implements the IDbConnectService interface and shows how connection failures and retry operations can be implemented.

public class DbConnectService : IDbConnectService
{
    private readonly CustomDbContext _dbContext;
    public DbConnectService(CustomDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    public async Task<Customer> GetCustomer(int customerId)
    {
        try
        {
            return await _dbContext.Customers.FindAsync(customerId);
        }
        catch (SqlException ex)
        {
            //Write your custom code here to handle
            // connection failure and retry the operation
            // or implement a fallback strategy
        }
        return await Task.FromResult<Customer>(null);
    }
}

The source code of the IDbConnectService interface is given below.

public interface IDbConnectService
{
    public Task<Customer> GetCustomer(int customerId);
}

Create a CustomDbContext class in EF Core

As noted above, you will usually specify your execution strategy in the OnConfiguring method of your custom DbContext class. The following code listing illustrates the CustomDbContext class that extends the DbContext class of EF Core and implements the OnConfiguring and OnModelCreating methods.

public class CustomContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public CustomContext(DbContextOptions options) : base(options)
    {
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
         optionsBuilder
           .UseSqlServer(
              @"Server= mssqldb;Database=Test;Trusted_Connection=True",
              options => options.EnableRetryOnFailure());
     }
     protected override void OnModelCreating(ModelBuilder modelBuilder)
     {
        //Write your custom code here to
        //configure the models used in your application
     }
}

A great way to handle database connection failures and make your application resilient is to use Polly, a fault-handling library for .NET. Polly is not a replacement for connection resiliency, but a complement. Polly allows you to implement thread-safe solutions for handling failures anywhere in your application. You can use Polly to implement circuit-breaker functionality so that any database connection failures in the application are handled gracefully. I’ll discuss using Polly to implement circuit breakers in a future post here.

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author

Exit mobile version