[PT-BR] Detectando se a aplicação está rodando para criar migrations

Desafios com Dependências "Scoped" no DbContext

No Entity Framework Core, o DbContext é frequentemente usado para interagir com o banco de dados. No entanto, em cenários complexos como aplicações multi-tenant, você pode precisar injetar serviços "scoped", algo como um TenantProvider, no seu DbContext. Isso pode criar desafios ao gerar migrações, pois a aplicação pode não conseguir resolver essas dependências durante o processo de migração. No entanto, existe uma solução para esse problema

Veja a seguinte classe como exemplo:

public class TenantProvider : ITenantProvider {
     public TenantProvider(IHttpContextAccessor httpContextAccessor) {...}
}
...
public class ApplicationDbContext : DbContext
{
    private readonly ITenantProvider _tenantProvider;

    // Constructor for runtime
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenantProvider tenantProvider)
        : base(options)
    {
        _tenantProvider = tenantProvider;
    }
}

Solução

Uma coisa que você pode fazer é adicionar mais um construtor, onde você adiciona manualmente as dependências, assim:

public TenantProvider(Tenant tenant)
{
     Tenant = tenant;
}

Depois, use o seguinte código para detectar se está sendo executado através do EF, ou seja, não haverá solicitação para capturar dados no HttpContext.

var isMigration = Assembly.GetEntryAssembly()?.FullName?.StartsWith("ef") ?? false;

Concluindo, lidar com dependências "scoped" no seu DbContext, especialmente em cenários complexos como aplicações multi-tenant, pode ser desafiador ao criar migrações. Adicionando um construtor adicional para injetar manualmente as dependências e detectando se a aplicação está sendo executada através do EF Core, você pode gerenciar essas dependências de forma eficaz. Essa abordagem permite que você alterne de dependências "scoped" para singletons conforme necessário, garantindo a criação suave de migrações. Alternativamente, usar o DesignTimeDbContextFactory é outra solução viável, embora mais complexa.

Exemplo completo de código

Aqui está o exemplo completo de código:

var isMigration = Assembly.GetEntryAssembly()?.FullName?.StartsWith("ef") ?? false;

if (isMigration)
{
     services.AddSingleton<ITenantProvider>(_ => new TenantProvider(tenantLocal));
}
else
{
     services.AddScoped<ITenantProvider, TenantProvider>();
}