EF
Core - Configurando o DbContext
![]() |
Hoje veremos como configurar o DbContext em tempo de projeto em aplicações ASP .NET Core. |
As ferramentas em tempo de projeto do EF Core, como Migrations, precisam ser capazes de descobrir e criar uma instância de trabalho do tipo DbContext para reunir detalhes sobre os tipos de entidade do aplicativo e como eles são mapeados para um esquema de banco de dados.
Este processo pode
ser automático, desde que a ferramenta possa criar facilmente o
DbContext de forma que ele seja configurado de
forma semelhante a como seria configurado em tempo de execução.
Embora qualquer padrão que forneça as informações de configuração necessárias
para o DbContext possa funcionar em tempo de
execução, as ferramentas que exigem o uso de um DbContext
em tempo de projeto só podem funcionar com um número limitado de padrões.
Configurando DbContextOptions
O DbContext deve ter uma instância de DbContextOptions para realizar qualquer trabalho. A instância DbContextOptions carrega informações de configuração como:
O exemplo a seguir configura o DbContextOptions para usar o provedor do SQL Server, uma conexão contida na variável connectionString, um tempo limite de comando no nível do provedor e um seletor de comportamento EF Core que torna todas as consultas executadas no DbContext sem rastreamento por padrão:
optionsBuilder .UseSqlServer(connectionString, providerOptions=>providerOptions.CommandTimeout(60)) .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); |
O
DbContextOptions pode ser fornecido ao DbContext
substituindo o método OnConfiguring ou externamente
por meio de um argumento do construtor.
Se ambos forem usados, o método OnConfiguring será
aplicado por último e pode sobrescrever as opções fornecidas para o argumento do
construtor.
Usando um construtor com argumento
Exemplo usando o construtor da classe de contexto com argumento aceitando um DbContextOptions:
public class DemoContext : DbContext
{
public DemoContext(DbContextOptions<DemoContext> options) : base(options)
{ }
public DbSet<Demo> Demos { get; set; }
}
|
Seu aplicativo agora pode passar as DbContextOptions ao instanciar um contexto, da seguinte maneira:
var optionsBuilder = new DbContextOptionsBuilder<DemoContext>(); optionsBuilder.UseSqlite("Data Source=blog.db"); using (var context = new DemoContext(optionsBuilder.Options))
{
//seu código
}
|
Usando o método OnConfiguring da classe de contexto
Você também pode inicializar DbContextOptions dentro do próprio contexto. Embora você possa usar essa técnica para configuração básica, normalmente ainda precisará obter certos detalhes de configuração de fora, por exemplo, uma string de conexão de banco de dados.
Isso pode ser
feito com uma API Configuration ou qualquer outro
meio.
Para inicializar DbContextOptions dentro do
contexto, substitua o método OnConfiguring e chame
os métodos no DbContextOptionsBuilder fornecido:
public class DemoContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=blog.db");
}
public DbSet<Demo> Demos { get; set; }
}
|
Com base nesta abordagem uma aplicação pode simplesmente instanciar tal contexto sem passar nada para seu construtor:
using (var context = new DemoContext())
{
// seu código
}
|
Outro exemplo de implementação do método OnConfiguring() onde definimos a obtenção da string de conexão a partir do arquivo appsettings.json :
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("DemoConnection");
options.UseSqlServer(connectionString);
}
}
|
Usando o DbContext com injeção de dependência
Esse talvez seja a abordagem mais usada. Como o EF Core suporta o uso de DbContext com um contêiner de injeção de dependência. Seu tipo DbContext pode ser adicionado ao contêiner de serviço usando o método AddDbContext<TContext>.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DemoContext>(options => options.UseSqlite("Data Source=blog.db"));
}
|
public class DemoContext : DbContext
{
public DemoContext(DbContextOptions<DemoContext> options) : base(options)
{ }
public DbSet<Demo> Demos { get; set; }
}
|
public class HomeController
{
private readonly DemoContext _context;
public HomeController(DemoContext context)
{
_context = context;
}
...
}
|
Isso inclui a
execução paralela de consultas assíncronas e qualquer uso simultâneo
explícito de várias threads. Portanto, sempre aguarde chamadas assíncronas (usando
await) imediatamente ou use instâncias DbContext separadas para
operações executadas em paralelo.
Quando o EF Core detecta uma tentativa de usar uma instância DbContext
simultaneamente, você terá um erro
InvalidOperationException com uma mensagem como
esta:
"Uma segunda operação foi iniciada neste contexto
antes da conclusão de uma operação anterior. Isso geralmente é causado por
threads diferentes usando a mesma instância de DbContext, no entanto, os membros
da instância não têm garantia de thread-safe."
Quando o acesso simultâneo não é detectado, isso pode resultar em comportamento
indefinido, travamentos de aplicativos e corrupção de dados.
Existem erros comuns que podem causar inadvertidamente acesso simultâneo na
mesma instância DbContext como:
1- Esquecer de esperar a conclusão de uma operação
assíncrona antes de iniciar qualquer outra operação no mesmo DbContext
Os métodos assíncronos permitem que o EF Core inicie operações que acessam o
banco de dados de forma não bloqueada. Mas se um chamador não esperar a
conclusão de um desses métodos e continuar a realizar outras operações no
DbContext, o estado do DbContext pode ser (e
muito provavelmente será) corrompido.
Assim, sempre utilize a palavra-chave await
nos métodos assíncronos do EF Core.
2- Compartilhamento implícito de instâncias DbContext
em várias threads por meio de injeção de dependência
O método de extensão AddDbContext registra tipos
DbContext com um tempo de vida com
Scoped por padrão.
Este modo é protegido contra problemas de acesso simultâneo na maioria dos
aplicativos ASP.NET Core porque há apenas um thread executando cada
solicitação do cliente em um determinado momento e porque cada solicitação obtém
um escopo de injeção de dependência separado (e, portanto, uma instância
DbContext separada).
Para o modelo de
hospedagem do Blazor Server, uma solicitação
lógica é usada para manter o circuito do usuário do Blazor e, portanto, apenas
uma instância DbContext com escopo está disponível por circuito do
usuário se o modo Scoped for usado.
Qualquer código que execute explicitamente vários threads em paralelo
deve garantir que as instâncias DbContext nunca sejam acessadas
simultaneamente.
Usando a injeção de dependência, isso pode ser obtido registrando o contexto
como Scoped e criando escopos (usando
IServiceScopeFactory) para cada thread ou registrando o DbContext
como temporário (usando a sobrecarga de AddDbContext que leva um parâmetro
ServiceLifetime).
Por isso ler a documentação é importante para entender o comportamento básico do contexto e evitar dores de cabeça e depois não saber porque o problema esta ocorrendo.
E estamos conversados...
(Disse Jesus)"Eu sou a videira
verdadeira, e meu Pai é o lavrador. Toda a vara em mim, que não dá fruto, a
tira; e limpa toda aquela que dá fruto, para que dê mais fruto."
João 15:1,2
Referências:
Entity Framework Core - Usando o SQLite
EF Core - Logando os comandos SQL no console
EF Core - Fundamentos : Componentes
EF Core 2.0 - Scaffolding DbContext e Models ..
ASP .NET Core - Implementando a segurança com ..
ASP.NET Core MVC - Criando um Dashboard ..
C# - Gerando QRCode - Macoratti
Entity Framework - Trabalhando com o DbContext
EF Core - Usando a abordagem DataBase First .
Usando o EF Core - DbContext e DbSet