Vida útil, configuração e inicialização do DbContext

Este artigo mostra padrões básicos para inicialização e configuração de uma DbContext instância do.

O 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 DbContext instância do foi projetada para ser usada para uma única unidade de trabalho. Isso significa que o tempo de vida de uma DbContext instância é geralmente muito curto.

Dica

Para citar Martin Fowler do link acima, "uma unidade de trabalho controla tudo o que você faz durante uma transação comercial que pode afetar o banco de dados. Quando terminar, ele calcula tudo o que precisa ser feito para alterar o banco de dados como resultado do seu trabalho. "

Uma unidade de trabalho típica ao usar Entity Framework Core (EF Core) envolve:

  • Criação de uma DbContext instância
  • Acompanhamento de instâncias de entidade pelo contexto. As entidades se tornam controladas por
  • As alterações são feitas nas entidades rastreadas conforme necessário para implementar a regra de negócio
  • SaveChanges ou SaveChangesAsync é chamado. EF Core detecta as alterações feitas e as grava no banco de dados.
  • A DbContext instância é descartada

Importante

  • É muito importante descartar o DbContext uso posterior. Isso garante que todos os recursos não gerenciados sejam liberados e que todos os eventos ou outros ganchos tenham o registro cancelado para evitar vazamentos de memória, caso a instância permaneça referenciada.
  • DbContext não é thread-safe. Não compartilhe contextos entre threads. Certifique-se de aguardar todas as chamadas assíncronas antes de continuar a usar a instância de contexto.
  • Um InvalidOperationException gerado pelo código de EF core pode colocar o contexto em um estado irrecuperável. Essas exceções indicam um erro de programa e não foram projetadas para serem recuperadas do.

DbContext em injeção de dependência para ASP.NET Core

Em muitos aplicativos Web, cada solicitação HTTP corresponde a uma única unidade de trabalho. Isso torna a vinculação do tempo de vida do contexto ao da solicitação um bom padrão para aplicativos Web.

ASP.NET Core aplicativos são configurados usando a injeção de dependência. EF Core pode ser adicionado a essa configuração usando AddDbContext o no ConfigureServices método de Startup.cs . Por exemplo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ApplicationDbContext>(
        options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}

Este exemplo registra uma DbContext subclasse chamada ApplicationDbContext como um serviço com escopo no provedor de serviços de aplicativo ASP.NET Core (também conhecido como o contêiner de injeção de dependência). O contexto é configurado para usar o provedor de banco de dados SQL Server e lerá a cadeia de conexão da configuração de ASP.NET Core. Normalmente, não importa onde a ConfigureServices chamada AddDbContext é feita.

A ApplicationDbContext classe deve expor um construtor público com um DbContextOptions<ApplicationDbContext> parâmetro. É assim que a configuração de contexto do AddDbContext é passada para o DbContext . Por exemplo:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

ApplicationDbContext pode ser usado em ASP.NET Core controladores ou outros serviços por meio de injeção de construtor. Por exemplo:

public class MyController
{
    private readonly ApplicationDbContext _context;

    public MyController(ApplicationDbContext context)
    {
        _context = context;
    }
}

O resultado final é uma ApplicationDbContext instância criada para cada solicitação e passada para o controlador para executar uma unidade de trabalho antes de ser descartada quando a solicitação termina.

Leia mais detalhadamente neste artigo para saber mais sobre as opções de configuração. Além disso, consulte inicialização do aplicativo em ASP.NET Core e injeção de dependência em ASP.NET Core para obter mais informações sobre a configuração e injeção de dependência no ASP.NET Core.

Inicialização de DbContext simples com ' New '

DbContext as instâncias podem ser construídas na forma normal do .NET, por exemplo, com o new em C#. A configuração pode ser executada substituindo o OnConfiguring método ou passando opções para o construtor. Por exemplo:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

Esse padrão também torna mais fácil passar a configuração como a cadeia de conexão por meio do DbContext Construtor. Por exemplo:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

    public ApplicationDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

Como alternativa, DbContextOptionsBuilder pode ser usado para criar um DbContextOptions objeto que é passado para o DbContext Construtor. Isso permite que um DbContext configurado para injeção de dependência também seja construído explicitamente. Por exemplo, ao usar ApplicationDbContext definido para aplicativos web ASP.NET Core acima:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

O DbContextOptions pode ser criado e o construtor pode ser chamado explicitamente:

var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test")
    .Options;

using var context = new ApplicationDbContext(contextOptions);

Usando uma fábrica de DbContext (por exemplo, para um mais incrivelmente)

Alguns tipos de aplicativos (por exemplo, ASP.NET Coremais úteis) usam injeção de dependência, mas não criam um escopo de serviço que se alinhe com o DbContext tempo de vida desejado. Mesmo onde tal alinhamento existe, o aplicativo pode precisar executar várias unidades de trabalho dentro desse escopo. Por exemplo, várias unidades de trabalho em uma única solicitação HTTP.

Nesses casos, AddDbContextFactory o pode ser usado para registrar uma fábrica para a criação de DbContext instâncias. Por exemplo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<ApplicationDbContext>(
        options =>
            options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));
}

A ApplicationDbContext classe deve expor um construtor público com um DbContextOptions<ApplicationDbContext> parâmetro. Esse é o mesmo padrão usado na seção ASP.NET Core tradicional acima.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

A DbContextFactory fábrica pode então ser usada em outros serviços por meio de injeção de construtor. Por exemplo:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

A fábrica injetada pode então ser usada para construir instâncias DbContext no código do serviço. Por exemplo:

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

Observe que as DbContext instâncias criadas dessa maneira não são gerenciadas pelo provedor de serviços do aplicativo e, portanto, devem ser descartadas pelo aplicativo.

Confira ASP.NET Core servidor mais incrivelmente com Entity Framework Core para saber mais sobre como usar EF core com mais de uma forma.

DbContextOptions

O ponto de partida para todas as DbContext configurações é DbContextOptionsBuilder . Há três maneiras de obter esse construtor:

  • Em AddDbContext e métodos relacionados
  • Em OnConfiguring
  • Construído explicitamente com new

Exemplos de cada um deles são mostrados nas seções anteriores. A mesma configuração pode ser aplicada independentemente de onde venha o construtor. Além disso, OnConfiguring é sempre chamado, independentemente de como o contexto é construído. Isso significa que OnConfiguring pode ser usado para executar uma configuração adicional, mesmo quando AddDbContext está sendo usado.

Configurando o provedor de banco de dados

Cada DbContext instância deve ser configurada para usar apenas um provedor de banco de dados. (Diferentes instâncias de um DbContext subtipo podem ser usadas com provedores de banco de dados diferentes, mas uma única instância deve usar apenas uma.) Um provedor de banco de dados é configurado usando uma Use* chamada específica. Por exemplo, para usar o provedor de banco de dados SQL Server:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

Esses Use* "métodos são métodos de extensão implementados pelo provedor de banco de dados. Isso significa que o pacote NuGet do provedor de banco de dados deve ser instalado antes que o método de extensão possa ser usado.

Dica

EF Core provedores de banco de dados fazem uso extensivo de métodos de extensão. Se o compilador indicar que um método não pode ser encontrado, certifique-se de que o pacote NuGet do provedor esteja instalado e que você tenha using Microsoft.EntityFrameworkCore; em seu código.

A tabela a seguir contém exemplos de provedores de banco de dados comuns.

Sistema de banco de dados Configuração de exemplo Pacote NuGet
SQL Server ou SQL do Azure .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
Banco de dados em memória do EF Core .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB * .UseMySql((connectionString) Pomelo.EntityFrameworkCore.MySql
Oracle* .UseOracle(connectionString) Oracle.EntityFrameworkCore
  • Esses provedores de banco de dados não são fornecidos pela Microsoft. Consulte provedores de banco de dados para obter mais informações sobre provedores de banco de dados.

Aviso

O EF Core banco de dados na memória não foi projetado para uso em produção. Além disso, pode não ser a melhor escolha mesmo para teste. Confira o código de teste que usa EF Core para obter mais informações.

Consulte cadeias de conexão para obter mais informações sobre como usar cadeias de conexão com EF Core.

A configuração opcional específica para o provedor de banco de dados é executada em um construtor específico de provedor adicional. Por exemplo, usando EnableRetryOnFailure para configurar novas tentativas de resiliência de conexão ao se conectar ao SQL do Azure:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test",
                providerOptions => { providerOptions.EnableRetryOnFailure(); });
    }
}

Dica

O mesmo provedor de banco de dados é usado para SQL Server e o SQL do Azure. No entanto, é recomendável que a resiliência de conexão seja usada ao se conectar a SQL Azure.

Consulte provedores de banco de dados para obter mais informações sobre a configuração específica do provedor.

Outra configuração de DbContext

Outras DbContext configurações podem ser encadeadas antes ou depois (não faz diferença) da Use* chamada. Por exemplo, para ativar o log de dados confidenciais:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

A tabela a seguir contém exemplos de métodos comuns chamados em DbContextOptionsBuilder .

Método DbContextOptionsBuilder O que faz Saiba mais
UseQueryTrackingBehavior Define o comportamento de rastreamento padrão para consultas Comportamento de rastreamento de consulta
LogTo Uma maneira simples de obter EF Core logs (EF Core 5,0 e posterior) Log, eventos e diagnósticos
UseLoggerFactory Registra uma Microsoft.Extensions.Logging fábrica Log, eventos e diagnósticos
EnableSensitiveDataLogging Inclui dados de aplicativos em exceções e registro em log Log, eventos e diagnósticos
EnableDetailedErrors Erros de consulta mais detalhados (às custas do desempenho) Log, eventos e diagnósticos
ConfigureWarnings Ignorar ou gerar avisos e outros eventos Log, eventos e diagnósticos
AddInterceptors Registra EF Core interceptores Log, eventos e diagnósticos
UseLazyLoadingProxies Usar proxies dinâmicos para carregamento lento Carregamento lento
UseChangeTrackingProxies Usar proxies dinâmicos para o controle de alterações Em breve...

Observação

UseLazyLoadingProxies e UseChangeTrackingProxies são métodos de extensão do pacote NuGet Microsoft. EntityFrameworkCore. proxies . Esse tipo de ". Chamada de UseSomething () "é a maneira recomendada para configurar e/ou usar EF Core extensões contidas em outros pacotes.

DbContextOptions vez DbContextOptions<TContext>

A maioria das DbContext subclasses que aceitam um DbContextOptions deve usar a variação genérica DbContextOptions<TContext> . Por exemplo:

public sealed class SealedApplicationDbContext : DbContext
{
    public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }
}

Isso garante que as opções corretas para o DbContext subtipo específico sejam resolvidas da injeção de dependência, mesmo quando vários DbContext subtipos são registrados.

Dica

Seu DbContext não precisa ser lacrado, mas o lacre é a prática recomendada para as classes não projetadas para serem herdadas.

No entanto, se o DbContext subtipo se destinar a ser herdado, ele deverá expor um construtor protegido usando um não genérico DbContextOptions . Por exemplo:

public abstract class ApplicationDbContextBase : DbContext
{
    protected ApplicationDbContextBase(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Isso permite que várias subclasses concretas chamem esse construtor base usando suas diferentes DbContextOptions<TContext> instâncias genéricas. Por exemplo:

public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
    public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
        : base(contextOptions)
    {
    }
}

public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
    public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
        : base(contextOptions)
    {
    }
}

Observe que esse é exatamente o mesmo padrão que ao herdar DbContext diretamente. Ou seja, o DbContext Construtor em si aceita um não genérico DbContextOptions por esse motivo.

Uma DbContext subclasse destinada a ser instanciada e herdada de deve expor ambas as formas de construtor. Por exemplo:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }

    protected ApplicationDbContext(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Configuração de DbContext de tempo de design

EF Core ferramentas de tempo de design como as para EF Core migrações precisam ser capazes de descobrir e criar uma instância de trabalho de um DbContext tipo para coletar detalhes sobre os tipos de entidade do aplicativo e como eles são mapeados para um esquema de banco de dados. Esse processo pode ser automático, contanto que a ferramenta possa criar facilmente o de DbContext forma que ele seja configurado da mesma forma que 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 a DbContext em tempo de design só podem funcionar com um número limitado de padrões. Eles são abordados em mais detalhes na criação de contexto em tempo de design.

Evitando problemas de threading de DbContext

Entity Framework Core não dá suporte a várias operações paralelas sendo executadas na mesma DbContext instância. Isso inclui a execução paralela de consultas assíncronas e qualquer uso simultâneo explícito de vários threads. Portanto, sempre await as chamadas assíncronas são feitas imediatamente ou usam DbContext instâncias separadas para operações executadas em paralelo.

Quando EF Core detectar uma tentativa de usar uma DbContext instância simultaneamente, você verá um 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, não há garantia de que os membros de instância sejam thread-safe.

Quando o acesso simultâneo deixa de ser detectado, ele pode resultar em comportamento indefinido, falhas do aplicativo e corrupção de dados.

Há erros comuns que podem causar inadvertidamente acesso simultâneo na mesma DbContext instância:

Armadilhas da operação assíncrona

Os métodos assíncronos permitem que EF Core iniciem operações que acessam o banco de dados de uma maneira sem bloqueio. Mas se um chamador não aguardar a conclusão de um desses métodos e continuar a executar outras operações no DbContext , o estado do DbContext pode ser (e provavelmente será) corrompido.

Sempre Await EF Core métodos assíncronos imediatamente.

Compartilhando instâncias DbContext implicitamente por meio de injeção de dependência

O AddDbContext método de extensão registra os DbContext tipos com um tempo de vida com escopo definido por padrão.

Isso é seguro contra problemas de acesso simultâneos na maioria dos aplicativos ASP.NET Core porque há apenas um thread executando cada solicitação de cliente em um determinado momento, e como cada solicitação Obtém um escopo de injeção de dependência separado (e, portanto, uma DbContext instância separada). Para um modelo de Hospedagem de servidor mais incrivelmente, uma solicitação lógica é usada para manter o circuito de usuário mais incrivelmente e, portanto, apenas uma instância DbContext com escopo estará disponível por circuito de usuário se o escopo de injeção padrão for usado.

Qualquer código que execute explicitamente vários threads em paralelo deve garantir que as DbContext instâncias não sejam acessadas simultaneamente.

Usando a injeção de dependência, isso pode ser feito registrando o contexto como escopo e criando escopos (usando IServiceScopeFactory ) para cada thread ou registrando o DbContext como transitório (usando a sobrecarga de AddDbContext que usa um ServiceLifetime parâmetro).

Mais leitura