EF Core  - Configurando o DBContext

 Neste artigo vamos recordar como configurar o DBContext no EF Core em aplicações ASP .NET Core.

O DbContext

Uma instância da classe DbContext representa uma combinação dos padrões de Unit of Work e Repository de forma que possa ser usada para consultar um banco de dados e agrupar alterações que serão gravadas de volta no armazenamento como uma unidade. (DbContext é conceitualmente semelhante a ObjectContext.)

Uma instância de DbContext representa uma camada de abstração em torno dos dados com os quais estamos lidando. Essa abstração existe entre os dados e os objetos Business/Domain (DDD) de um aplicativo. Ela permite que você acesse o banco de dados como um objeto (OOP) de forma simples e fácil, e, não apenas isola os objetos de negócios do código de acesso ao banco de dados, mas também fornece uma separação clara de interesses.

Assim, configurar o DbContext no Entity Framework é importante para estabelecer a conexão e executar as operações CRUD de forma eficiente no armazenamento de dados de sua escolha.

Configurando o provedor de banco de dados padrão

Esta deve ser a primeira coisa que faremos ao configurar a instância DbContext, e, geralmente usamos a classe DbContextOptions que representa as opções usada pelo DbContext.

Normalmente sobrescrevemos o método OnConfiguring() ou usamos um DbContextOptionsBuilder para criar instâncias dessa classe. Podemos usar DbContextOptions no construtor da classe de contexto para configurar os detalhes do DbContext como provedor do banco de dados, string de conexão, etc.

Para definir as opções do DbContext usamos o método AddDbContext() no método ConfigureServices da classe Startup para injetar o DbContext no contêiner IoC:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
     services.AddControllersWithViews();
}

No código acima temos :

-  O registro da nossa classe de contexto ApplicationDbContext para que fique disponível via injeção de dependência;

- A utilização de DbContextOptionsBuilder que usa o método de extensão UseSQLServer(), que registra o provedor de banco de dados SQL Server a ser usado com o EF Core;

- E a passagem da string de conexão para o método UseSqlServer();

No arquivo de contexto definimos o DbContextOptions<T> que carrega a informação da configuração para o DbContext:

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        { }
        public DbSet<Cliente> Clientes { get; set; }
    }

Tempo de vida do DbContext

O tempo de vida de um DbContext começa quando a instância é criada e termina quando a instância é descartada. Uma instância do DbContext foi projetada para ser usada para uma única Unit of Work. Isso significa que o tempo de vida de uma instância DbContext é geralmente muito curto.

Podemos usar o DbContext com o contêiner de injeção de dependência e isso garante que o DbContext seja criado de acordo com a solicitação do pipeline da API e descartado com base no gerenciamento de tempo de vida usado durante o registro.

Para isso podemos adicionar o tipo DbContext ao contêiner de serviço usando o método AddDbContext com tempo de vida do AddScoped o que garante que estamos registrando o DbContext como um objeto com escopo definido. ou seja, cada nova solicitação usará os serviços injetados e, portanto, o novo DbContext.

Podemos usar o DbContext sem a injeção de dependência, e, assim torna-se responsabilidade do implementador manter o controle de tais recursos e descartá-los manualmente, em vez de compilá-los para você (como vimos acima usando DI).

Outra opção é criar as DbContextOptions externamente e passá-las enquanto cria o contexto, conforme mostrado no exemplo a seguir :

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();

optionsBuilder.UseSqlServer(connectionString);

db = new EFContext(optionsBuilder.Options);

Se não estiver usando DI - então é recomendado usar a instrução "Using" para criar os recursos e descartá-los após seu uso:

using (var context = new EmployeeContext())
{
   lsitaClientes = _clienteContext.Clientes.AsEnumerable().Select(x => x.ClienteId).ToList();
}

Acessando o DbContext no Repositório ou outros serviços

Finalmente, podemos usar o contexto no controlador ou em outros serviços usando a injeção de dependência, conforme mostrado abaixo :

    private readonly ApplicationDbContext db;
 
    public HomeController(ApplicationDbContext EFContext)
    {
        db = EFContext;
    }

Neste cenário é melhor manter a mesma instância vitalícia (AddScoped) para o Repositório/Outros serviços personalizados e DBContext para evitar qualquer problema relacionado à corrupção de dados:

public void ConfigureServices(IServiceCollection services)
{
     services.AddScoped<IClienteRepository, ClienteRepository>();

    services.AddDbContext<ApplicationDbContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
     services.AddControllersWithViews();
}

A concorrência com o DbContext

O objeto DBContext não é seguro para threads, usar AddDbContext(), entretanto, garante que você está registrando o DBContext como o tempo de vida AddScoped. ou seja, cada nova solicitação usará a nova instância de serviço e, portanto, o novo DBContext.

Portanto, idealmente, a simultaneidade é implementada por solicitação básica, evitando quaisquer problemas de corrupção de dados. No entanto, ele deve ser protegido de qualquer acesso paralelo dentro do mesmo contexto de solicitação.

É responsabilidade do implementador implementar a simultaneidade ao usá-la em operações Task/Multithread.

Como boa prática, certifique-se de usar a operação síncrona e assíncrona corretamente no código :

public async Task<IActionResult> GetIdAsync(int? id)
{
   if (id == null)
   {
         return NotFound();
   }
   Cliente db = await _clienteContext.Clientes.FindAsync(id);
   
   if (db == null)
   {
         return NotFound();
   }
    return Ok(db)
}

E estamos conversados...

"Porque Deus não nos destinou para a ira, mas para a aquisição da salvação, por nosso Senhor Jesus Cristo,
Que morreu por nós, para que, quer vigiemos, quer durmamos, vivamos juntamente com ele."
1 Tessalonicenses 5:9,10

Referências:


José Carlos Macoratti