Um aplicativo de alta qualidade deve ser estável, de alto desempenho, escalonável e confiável. A confiabilidade de uma aplicação depende de muitos fatores, mas um dos mais importantes é a resiliência, ou a capacidade de resistir a falhas ou falhas durante o tempo de execução. Neste artigo, veremos como podemos trazer resiliência às conexões de banco de dados no ASP.NET Core usando o Entity Framework Core.
O EF Core tem um recurso chamado resiliência de conexão que repete automaticamente comandos de banco de dados com falha para manter a conectividade durante erros transitórios ou instabilidade da rede. Ao encapsular a lógica de identificação de falhas e repetição de comandos, esse recurso nos permite traçar planos de execução para diferentes situações de falha de banco de dados.
Para usar os exemplos de código fornecidos neste artigo, você deve ter o Visual Studio 2022 instalado em seu sistema. Se ainda não tiver uma cópia, você pode baixar o Visual Studio 2022 aqui.
Crie um projeto de API Web ASP.NET Core no Visual Studio 2022
Para criar um projeto de API Web ASP.NET Core no Visual Studio 2022, siga as etapas descritas abaixo.
- Inicie o IDE do Visual Studio 2022.
- Clique em “Criar novo projeto”.
- Na janela “Criar novo projeto”, selecione “API Web ASP.NET Core” na lista de modelos exibida.
- Clique em Avançar.
- Na janela “Configure seu novo projeto”, especifique o nome e o local do novo projeto. Opcionalmente, marque a caixa de seleção “Colocar solução e projeto no mesmo diretório”, dependendo de suas preferências.
- Clique em Avançar.
- Na janela “Informações Adicionais”, selecione “.NET 8.0 (Long Term Support)” como a versão da estrutura e certifique-se de que a caixa “Usar controladores” esteja marcada. Estaremos usando controladores neste projeto.
- Em outra parte da janela “Informações adicionais”, deixe o “Tipo de autenticação” definido como “Nenhum” (o padrão) e certifique-se de que as caixas de seleção “Ativar suporte à API aberta”, “Configurar para HTTPS” e “Ativar Docker” permaneçam desmarcadas. Não usaremos nenhum desses recursos aqui.
- Clique em Criar.
Usaremos este projeto de API Web do ASP.NET Core para trabalhar com os exemplos de código nas seções abaixo.
Crie uma estratégia de execução no EF Core
No EF Core, uma estratégia de execução é definida como um componente que encapsula a lógica para lidar com erros de execução de comandos do banco de dados e repeti-los se os erros forem considerados transitórios. Uma estratégia de execução permite que os desenvolvedores garantam que seus aplicativos possam se recuperar normalmente de erros transitórios, sem exigir intervenção humana.
Você pode criar uma estratégia de execução usando o método CreateExecutionStrategy conforme mostrado no trecho de código fornecido a seguir.
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(); });
Como você pode ver no exemplo de código a seguir, uma estratégia de execução geralmente é especificada no método OnConposing de sua classe DbContext personalizada.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer( @"Server= mssqldb;Database=Test;Trusted_Connection=True", options => options.EnableRetryOnFailure()); }
Se você estiver usando o Banco de Dados SQL do Azure, o EF Core já fornece a resiliência e a lógica de nova tentativa para seu banco de dados. No entanto, você deverá habilitar a estratégia de execução do EF Core para cada conexão DbContext que seu aplicativo fizer se quiser aproveitar a resiliência da conexão do EF Core. O trecho de código a seguir ilustra como você pode habilitar conexões SQL resilientes usando o EF Core que são repetidas sempre que a conexão com o banco de dados cai.
builder.Services.AddDbContext<LibraryContext>(options => { options.UseSqlServer(builder.Configuration("IDGConnectionString"), sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(45)); }); });
Use resiliência de conexão com transações no EF Core
Se você tiver habilitado novas tentativas no EF Core, todas as chamadas para o método SaveChanges serão repetidas como uma unidade se ocorrer uma falha na conexão do banco de dados. No entanto, se você executar um bloco de transação no código do seu aplicativo, usando o método BeginTransaction, deverá invocar explicitamente uma estratégia de execução usando um delegado para garantir que todas as operações dentro da transação sejam executadas. Isso é mostrado no trecho de código fornecido abaixo.
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(); });
Lidar com falhas de conexão de banco de dados no ASP.NET Core
Ao trabalhar com conexões de banco de dados usando o EF Core, você deve lidar com possíveis falhas de conexão capturando exceções e repetindo as operações do banco de dados. Considere a seguinte classe de entidade chamada Cliente.
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; }
A listagem de código a seguir ilustra a classe DbConnectService que implementa a interface IDbConnectService e mostra como falhas de conexão e operações de nova tentativa podem ser implementadas.
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); } }
O código-fonte da interface IDbConnectService é fornecido abaixo.
public interface IDbConnectService { public Task<Customer> GetCustomer(int customerId); }
Crie uma classe CustomDbContext no EF Core
Conforme observado acima, você normalmente especificará sua estratégia de execução no método OnConfigurando de sua classe DbContext personalizada. A listagem de código a seguir ilustra a classe CustomDbContext que estende a classe DbContext do EF Core e implementa os métodos OnConposing e OnModelCreating.
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 } }
Uma ótima maneira de lidar com falhas de conexão de banco de dados e tornar seu aplicativo resiliente é usar Polly, uma biblioteca de tratamento de falhas para .NET. Você pode usar o Polly para implementar a funcionalidade do disjuntor para que quaisquer falhas de conexão com o banco de dados no aplicativo sejam tratadas normalmente. Discutirei o uso do Polly para implementar disjuntores em uma postagem futura aqui.