DEV Community

Otavio Augusto
Otavio Augusto

Posted on

Singleton vs Scoped vs Transient: Entenda os Lifetimes da Injeção de Dependência

Quando começamos a trabalhar com ASP.NET Core, uma das primeiras coisas que aprendemos é a Injeção de Dependência (Dependency Injection).

Mas existe uma dúvida que acompanha muitos desenvolvedores por anos:

Quando devo usar Singleton, Scoped ou Transient?

Se você já ficou em dúvida sobre qual Lifetime utilizar ou já recebeu erros estranhos envolvendo DbContext, este artigo é para você.

Vamos entender esses conceitos de uma forma simples e prática.


Antes de tudo: o que é um Lifetime?

Imagine que sua aplicação é um hotel.

Os serviços registrados no container de Injeção de Dependência são hóspedes.

O Lifetime define:

Quanto tempo esse hóspede ficará hospedado antes de ir embora.

Dependendo da configuração escolhida, um objeto poderá:

  • Existir durante toda a aplicação
  • Existir apenas durante uma requisição
  • Ser recriado sempre que solicitado

É exatamente isso que Singleton, Scoped e Transient fazem.


Singleton

🏨 Analogia: O gerente do hotel

Imagine o gerente do hotel.

Existe apenas um.

Não importa quantos hóspedes cheguem ou quantos funcionários precisem de ajuda.

Todos falam com a mesma pessoa.

Request 1
     \
Request 2 ---> Gerente Único
     /
Request 3
Enter fullscreen mode Exit fullscreen mode

O que acontece na aplicação?

O ASP.NET Core cria apenas uma instância e a reutiliza durante toda a vida da aplicação.

builder.Services.AddSingleton<EmailService>();
Enter fullscreen mode Exit fullscreen mode
Request 1 -> Instância A
Request 2 -> Instância A
Request 3 -> Instância A
Enter fullscreen mode Exit fullscreen mode

Sempre a mesma instância.


Quando usar?

✅ Cache

✅ Configurações

✅ Serviços sem estado

✅ HttpClientFactory

✅ Clients reutilizáveis


⚠️ Cuidado com estado compartilhado

Imagine que você registre o seguinte serviço:

public class UserContext
{
    public string Username { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

E registre como Singleton:

builder.Services.AddSingleton<UserContext>();
Enter fullscreen mode Exit fullscreen mode

Agora todos os usuários da aplicação compartilharão a mesma instância.

Isso significa que informações de um usuário podem sobrescrever informações de outro.

Por esse motivo, Singleton deve ser utilizado apenas quando o serviço não possui dados específicos de usuários.


Scoped

🍽️ Analogia: O garçom do restaurante

Agora imagine os garçons.

Cada mesa recebe um garçom específico.

Durante todo o atendimento, aquela mesa conversa com o mesmo garçom.

Quando a refeição termina, o atendimento acaba.

Mesa 1 -> Garçom A

Mesa 2 -> Garçom B

Mesa 3 -> Garçom C
Enter fullscreen mode Exit fullscreen mode

O que acontece na aplicação?

Uma nova instância é criada para cada requisição HTTP.

builder.Services.AddScoped<UserService>();
Enter fullscreen mode Exit fullscreen mode
Request 1 -> Instância A

Request 2 -> Instância B

Request 3 -> Instância C
Enter fullscreen mode Exit fullscreen mode

Quando usar?

Na prática, esse é o Lifetime mais utilizado em aplicações ASP.NET Core.

✅ DbContext

✅ Repositories

✅ Services de negócio

✅ Unit Of Work


Exemplo real

O próprio Entity Framework registra o DbContext como Scoped.

builder.Services.AddDbContext<AppDbContext>();
Enter fullscreen mode Exit fullscreen mode

Cada requisição recebe seu próprio contexto.

Isso evita conflitos entre usuários.


💡 Por que o DbContext é Scoped?

Imagine duas requisições simultâneas:

Request A
Request B
Enter fullscreen mode Exit fullscreen mode

Se ambas compartilhassem o mesmo DbContext, poderiam ocorrer conflitos de rastreamento de entidades, concorrência e inconsistências nos dados.

Ao utilizar Scoped, cada requisição recebe sua própria instância.

Por isso o Entity Framework registra o DbContext dessa forma por padrão.


Transient

🥤 Analogia: Um copo descartável

Imagine um copo descartável.

Você pega.

Usa.

Descarta.

Precisa de outro?

Recebe um novo.

Sempre.

Uso 1 -> Copo A

Uso 2 -> Copo B

Uso 3 -> Copo C
Enter fullscreen mode Exit fullscreen mode

O que acontece na aplicação?

Uma nova instância é criada toda vez que o serviço é solicitado.

builder.Services.AddTransient<LogFormatter>();
Enter fullscreen mode Exit fullscreen mode

Mesmo dentro da mesma requisição:

Resolução 1 -> Instância A

Resolução 2 -> Instância B

Resolução 3 -> Instância C
Enter fullscreen mode Exit fullscreen mode

Quando usar?

✅ Formatadores

✅ Conversores

✅ Geradores de PDF

✅ Geradores de Token

✅ Serviços extremamente leves


Comparativo rápido

Característica Singleton Scoped Transient
Uma instância para toda aplicação
Uma instância por request
Nova instância sempre
Compartilha estado Apenas na request
Mais utilizado

Aproximadamente 80% dos serviços em aplicações ASP.NET Core acabam sendo registrados como Scoped.


O erro que quase todo desenvolvedor já viu

Registrar um Singleton que depende de um Scoped.

builder.Services.AddSingleton<UserService>();
builder.Services.AddScoped<AppDbContext>();
Enter fullscreen mode Exit fullscreen mode
public class UserService
{
    public UserService(AppDbContext context)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

Resultado:

Cannot consume scoped service
from singleton
Enter fullscreen mode Exit fullscreen mode

O motivo é simples:

  • O Singleton vive durante toda a aplicação.
  • O Scoped vive apenas durante a requisição.

O container não consegue manter uma referência válida para um objeto que será destruído antes dele.


Resumo Visual

Lifetimes da Injeção de Dependência


Conclusão

Singleton, Scoped e Transient parecem apenas detalhes da Injeção de Dependência.

Mas eles impactam diretamente:

  • Performance
  • Consumo de memória
  • Escalabilidade
  • Concorrência
  • Segurança dos dados

Entender os Lifetimes da Injeção de Dependência vai muito além de decorar três palavras.

Eles definem como os objetos da sua aplicação vivem, são compartilhados e são descartados.

Se você estiver em dúvida:

Singleton -> Uma instância para toda aplicação

Scoped -> Uma instância por requisição

Transient -> Uma nova instância sempre
Enter fullscreen mode Exit fullscreen mode

Na maioria dos projetos ASP.NET Core:

DbContext      -> Scoped
Repositories   -> Scoped
Services       -> Scoped

Cache          -> Singleton
Configurações  -> Singleton

Formatadores   -> Transient
Conversores    -> Transient
Geradores      -> Transient
Enter fullscreen mode Exit fullscreen mode

Dominar esses conceitos é um dos primeiros passos para construir aplicações robustas, escaláveis e fáceis de manter.


💬 E você?

Qual Lifetime você mais utiliza nos seus projetos hoje?

Top comments (0)