Você cria um BackgroundService, injeta um DbContext no construtor, roda — e por um tempo parece funcionar. Até começarem os bugs que ninguém consegue reproduzir: estado obsoleto, conexões descartadas, dados que não batem. Esse é, talvez, o erro mais comum ao construir hosted services no .NET.
Ao final deste post você vai entender por que esse padrão quebra os tempos de vida (lifetimes) das suas dependências e qual é o jeito correto de acessar serviços scoped dentro de um hosted service.
O conflito de tempos de vida
Aqui está o detalhe que muita gente não percebe: hosted services são registrados como Singletons. Uma única instância é criada para toda a vida da aplicação.
Só que muitos serviços comuns no ASP.NET são scoped — uma instância por escopo (tipicamente, por requisição web). Os exemplos clássicos:
-
DbContext(Entity Framework) - Repositórios
- Serviços de unit of work
Quando você registra um DbContext, isso acontece de forma padrão:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
AddDbContext registra o AppDbContext como scoped. Esse é o comportamento padrão do EF Core, e é o correto para o ciclo de uma requisição.
Por que injetar direto quebra tudo
Um hosted service vive durante toda a vida da aplicação. Um serviço scoped foi feito para viver apenas dentro de um escopo específico — por exemplo, uma requisição web.
Então, se você injetar a dependência scoped diretamente no construtor do hosted service, você viola esse limite de tempo de vida:
public class JobProcessor : BackgroundService
{
private readonly AppDbContext _dbContext; // ⚠️ scoped dentro de um singleton
public JobProcessor(AppDbContext dbContext) // isto é o que NÃO queremos
{
_dbContext = dbContext;
}
// ...
}
As consequências de quebrar essa fronteira:
- Tempos de vida incorretos de serviço
- Dependências descartadas sendo usadas
- Estado obsoleto preso em memória pela vida inteira da aplicação
- Comportamento imprevisível geral
E imprevisibilidade é a última coisa que você quer quando o assunto é falar com o banco de dados.
O padrão correto: IServiceScopeFactory
Em vez de injetar a dependência scoped, você injeta um IServiceScopeFactory e cria um escopo sempre que precisar resolver serviços scoped.
A fábrica de escopos foi projetada exatamente para isso: te entregar um escopo que você controla, dentro do seu hosted service.
public class JobProcessor : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public JobProcessor(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Verificando jobs...");
// 1. cria um escopo (com using, para ser descartado depois)
using var scope = _scopeFactory.CreateScope();
// 2. resolve o DbContext dentro desse escopo
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 3. usa normalmente
await ProcessJob(dbContext);
await Task.Delay(1000, stoppingToken);
}
}
}
Os três pontos que fazem isso funcionar:
-
CreateScope()dentro do loop cria umIServiceScopenovo a cada iteração. - A declaração
usinggarante que o escopo (e tudo o que ele resolveu) seja descartado ao final. -
GetRequiredService<AppDbContext>()resolve oDbContexta partir doServiceProviderdaquele escopo — respeitando o lifetime scoped.
A ideia central: você cria o escopo para o DbContext porque o DbContext é scoped, mas o lugar onde o processamento acontece roda como singleton. O escopo é a ponte segura entre os dois mundos.
O aprendizado que evita horas de debug
Guarde essa frase: um hosted service é um singleton. Então, sempre que precisar de uma dependência scoped — como um DbContext — você deve criar um escopo primeiro.
Fazer isso garante duas coisas críticas:
- As dependências scoped são criadas corretamente a cada uso.
- E, igualmente importante, são descartadas corretamente ao final — sem conexões vazando nem estado preso por toda a vida da aplicação.
Conclusão
Injetar um DbContext direto no construtor de um BackgroundService é o tipo de erro que não estoura na hora — ele apodrece silenciosamente até virar um bug intermitente em produção. A correção é barata: injete IServiceScopeFactory, crie um escopo com using dentro do loop e resolva suas dependências scoped a partir dele.
Abra seu hosted service agora e confira: tem algum serviço scoped no construtor? Se tiver, você já sabe o que refatorar. No próximo post, vamos falar sobre outro pilar de hosted services em produção: graceful shutdown e o CancellationToken que você não pode ignorar.














