# Tempo de Vida do DbContext no ASP.NET Core: Performance vs Segurança de Threads em Operações Async > Domine o gerenciamento do tempo de vida do DbContext no ASP.NET Core. Aprenda quando usar scoped vs transient, como lidar com operações async de forma segura e otimizar performance com pooling de DbContext. - Published: 2026-09-07 - Updated: 2026-09-07 - Author: Anthony Fillion-Maillet - Tags: dotnet, entity-framework, aspnet-core, async, performance - Reading time: 10 min --- O gerenciamento do tempo de vida do DbContext é uma das fontes mais comuns de bugs em aplicações ASP.NET Core. O DbContext do Entity Framework Core não é thread-safe, e seu uso inadequado em operações assíncronas causa estados corrompidos, condições de corrida e exceções difíceis de reproduzir. > **DbContext não é thread-safe** > > Uma única instância de DbContext não pode ser usada simultaneamente em múltiplas threads. Se um método async não for aguardado antes que outra operação comece no mesmo contexto, o estado interno se corrompe. Esta regra se aplica a todas as versões do EF Core, incluindo o EF Core 9. ## Por Que o Tempo de Vida Scoped Funciona para Requisições Web A injeção de dependências do ASP.NET Core registra o DbContext com tempo de vida scoped por padrão ao usar `AddDbContext()`. Cada requisição HTTP recebe sua própria instância de DbContext, e essa instância é destruída quando a requisição termina. Essa abordagem resolve dois problemas: garante que cada requisição tenha um estado de banco de dados isolado e alinha o tempo de vida do contexto com o padrão Unit of Work, onde as mudanças se acumulam durante o processamento da requisição e são confirmadas juntas no final. ```csharp // Program.cs builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); ``` O tempo de vida scoped é apropriado para fluxos padrão de requisição-resposta. Uma action de controller ou handler de Razor Page executa em uma única thread, e desde que todas as chamadas async sejam corretamente aguardadas, o DbContext permanece em um estado consistente durante toda a requisição. ```csharp // OrderController.cs public class OrderController : ControllerBase { private readonly AppDbContext _context; public OrderController(AppDbContext context) { _context = context; } [HttpPost] public async Task CreateOrder(CreateOrderDto dto) { var order = new Order { CustomerId = dto.CustomerId, Total = dto.Total }; _context.Orders.Add(order); await _context.SaveChangesAsync(); return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order); } } ``` A restrição chave: cada operação async deve ser completada antes que a próxima comece. O código acima está correto porque `SaveChangesAsync` é aguardado antes do método retornar. ## O Problema de Thread Safety em Operações Paralelas Os problemas surgem quando desenvolvedores tentam paralelizar operações de banco de dados dentro de uma mesma requisição. Considere este exemplo problemático: ```csharp // DEFEITUOSO: Não usar public async Task GetDashboard() { var ordersTask = _context.Orders.CountAsync(); var productsTask = _context.Products.CountAsync(); var customersTask = _context.Customers.CountAsync(); // Executando três consultas no mesmo DbContext simultaneamente await Task.WhenAll(ordersTask, productsTask, customersTask); return Ok(new { Orders = ordersTask.Result, Products = productsTask.Result, Customers = customersTask.Result }); } ``` Este código inicia três consultas async sem aguardar cada uma individualmente. As três operações executam concorrentemente na mesma instância de DbContext, violando os requisitos de thread safety do EF Core. O resultado é imprevisível: às vezes funciona, às vezes lança `InvalidOperationException`, e às vezes retorna dados incorretos. A [documentação oficial da Microsoft](https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/) afirma explicitamente que métodos async devem ser aguardados imediatamente. O change tracker interno, o estado de conexão e o cache de compilação de queries não foram projetados para acesso concorrente. ## Usando IDbContextFactory para Consultas Paralelas Quando operações de banco de dados paralelas são genuinamente necessárias, `IDbContextFactory` fornece a solução. Esta factory cria novas instâncias de DbContext sob demanda, cada uma com seu próprio estado isolado. ```csharp // Program.cs builder.Services.AddDbContextFactory(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); ``` Com a factory registrada, injete-a em vez do DbContext diretamente: ```csharp // DashboardService.cs public class DashboardService { private readonly IDbContextFactory _contextFactory; public DashboardService(IDbContextFactory contextFactory) { _contextFactory = contextFactory; } public async Task GetStatsAsync() { // Cada task recebe sua própria instância de DbContext var ordersTask = Task.Run(async () => { await using var context = _contextFactory.CreateDbContext(); return await context.Orders.CountAsync(); }); var productsTask = Task.Run(async () => { await using var context = _contextFactory.CreateDbContext(); return await context.Products.CountAsync(); }); var customersTask = Task.Run(async () => { await using var context = _contextFactory.CreateDbContext(); return await context.Customers.CountAsync(); }); await Task.WhenAll(ordersTask, productsTask, customersTask); return new DashboardStats { Orders = ordersTask.Result, Products = productsTask.Result, Customers = customersTask.Result }; } } ``` Cada task cria, usa e destrói seu próprio contexto. A instrução `await using` garante a limpeza adequada mesmo se uma exceção ocorrer. ## Pooling de DbContext para Aplicações de Alta Performance Criar um novo DbContext envolve alocação de memória, inicialização do change tracker e configuração de caches internos. Para aplicações de alta performance processando milhares de requisições por segundo, essa sobrecarga se torna mensurável. O pooling de DbContext resolve isso reutilizando instâncias de contexto. Quando um contexto do pool é destruído, o EF Core reseta seu estado e o retorna ao pool em vez de deixá-lo para o garbage collector. ```csharp // Program.cs builder.Services.AddDbContextPool(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default")), poolSize: 128); ``` Os [benchmarks de Dave Callan](https://davecallan.com/entity-framework-dbcontext-pooling-performance-benchmark/) mostram que o pooling reduz o tempo de criação do contexto em mais de 90% em microbenchmarks. No entanto, o impacto real depende dos padrões de consulta da aplicação. Se as consultas de banco de dados dominam o tempo de processamento de requisições, a sobrecarga de criação do contexto é insignificante em comparação. O pooling introduz uma restrição: o estado do DbContext é resetado ao retornar ao pool. Quaisquer campos ou propriedades customizados adicionados à classe DbContext perderão seus valores. O change tracker é limpo, então mudanças não confirmadas desaparecem. ## Combinando Pooling com IDbContextFactory Para aplicações que precisam tanto da performance do pooling quanto do suporte a consultas paralelas, usa-se `AddPooledDbContextFactory`: ```csharp // Program.cs builder.Services.AddPooledDbContextFactory(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default")), poolSize: 128); ``` Este registro fornece `IDbContextFactory` onde os contextos vêm do pool. Cada chamada a `CreateDbContext()` recupera uma instância do pool, e destruí-la retorna a instância ao pool. ```csharp // BatchProcessor.cs public class BatchProcessor { private readonly IDbContextFactory _contextFactory; public BatchProcessor(IDbContextFactory contextFactory) { _contextFactory = contextFactory; } public async Task ProcessBatchAsync(IEnumerable updates) { // Processar em chunks paralelos com contextos do pool var chunks = updates.Chunk(100); var tasks = chunks.Select(async chunk => { await using var context = _contextFactory.CreateDbContext(); foreach (var update in chunk) { var order = await context.Orders.FindAsync(update.OrderId); if (order != null) { order.Status = update.NewStatus; } } await context.SaveChangesAsync(); }); await Task.WhenAll(tasks); } } ``` A [análise de Milan Jovanovic](https://milanjovanovic.tech/blog/ef-core-dbcontext-pooling) fornece benchmarks adicionais mostrando a performance da factory com pool em cenários de processamento em lote. ## Blazor Server: Um Caso Especial Aplicações Blazor Server requerem cuidado extra com o tempo de vida do DbContext. Um circuito Blazor persiste através de múltiplas interações do usuário, diferente das requisições HTTP que têm limites claros. O tempo de vida scoped padrão se torna problemático: uma única instância de DbContext vive durante toda a duração do circuito, potencialmente horas. Contextos de longa vida acumulam entidades rastreadas, aumentando o uso de memória e deixando as operações mais lentas. ```csharp // Program.cs para Blazor Server builder.Services.AddDbContextFactory(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); ``` Nos componentes Blazor, injete a factory e crie contextos de curta vida: ```csharp // OrderList.razor.cs public partial class OrderList : ComponentBase { [Inject] private IDbContextFactory ContextFactory { get; set; } = default!; private List _orders = new(); protected override async Task OnInitializedAsync() { await using var context = ContextFactory.CreateDbContext(); _orders = await context.Orders .AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Take(50) .ToListAsync(); } } ``` A chamada `AsNoTracking()` é particularmente importante no Blazor Server. Sem ela, cada entidade carregada permanece no change tracker, consumindo memória até o circuito terminar. ## Serviços em Background e Hosted Services Serviços em background registrados como singletons não podem injetar DbContext scoped diretamente. O serviço sobrevive a qualquer scope, criando erros de incompatibilidade de tempo de vida. ```csharp // OrderProcessingService.cs public class OrderProcessingService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; public OrderProcessingService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var pendingOrders = await context.Orders .Where(o => o.Status == OrderStatus.Pending) .Take(10) .ToListAsync(stoppingToken); foreach (var order in pendingOrders) { order.Status = OrderStatus.Processing; } await context.SaveChangesAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } } ``` O `IServiceScopeFactory` cria um novo scope para cada iteração. O scope, e o DbContext dentro dele, é destruído no final de cada iteração do loop. Alternativamente, pode-se usar `IDbContextFactory` para um controle mais granular: ```csharp // OrderProcessingService.cs (versão factory) public class OrderProcessingService : BackgroundService { private readonly IDbContextFactory _contextFactory; public OrderProcessingService(IDbContextFactory contextFactory) { _contextFactory = contextFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await using var context = _contextFactory.CreateDbContext(); var pendingOrders = await context.Orders .Where(o => o.Status == OrderStatus.Pending) .Take(10) .ToListAsync(stoppingToken); foreach (var order in pendingOrders) { order.Status = OrderStatus.Processing; } await context.SaveChangesAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } } ``` ## Erros Comuns que Causam Violações de Thread Safety Vários padrões consistentemente causam problemas de thread safety do DbContext em código de produção: **Armazenar DbContext em campos estáticos ou singletons**: Um DbContext nunca deve sobreviver ao seu scope pretendido. Referências estáticas tornam a mesma instância acessível a partir de múltiplas threads. **Chamadas async fire-and-forget**: Iniciar uma operação async sem aguardá-la enquanto continua usando o contexto na thread atual cria acesso concorrente. ```csharp // DEFEITUOSO: Não usar public void UpdateAndNotify(int orderId) { var order = _context.Orders.Find(orderId); order.Status = OrderStatus.Shipped; _context.SaveChangesAsync(); // Não aguardado! _notificationService.SendAsync(order.CustomerId); // O contexto pode ainda estar salvando } ``` **Injetar DbContext em serviços singleton**: O container de DI lança uma exceção em desenvolvimento, mas algumas configurações mascaram esse erro. **Usar DbContext através de múltiplos awaits sem entender o fluxo de execução**: Cada await é um ponto de suspensão. Se o código retomado executa em uma thread diferente da esperada, acesso concorrente pode ocorrer com outros caminhos de código. ## Guia de Decisão para Configuração do Tempo de Vida do DbContext Escolher a configuração correta de DbContext depende do tipo de aplicação e requisitos de performance: - **API web padrão ou aplicação MVC**: Usar `AddDbContext()` com o tempo de vida scoped padrão. Isso cobre a maioria dos cenários corretamente. - **API de alta performance (milhares de requisições/segundo)**: Usar `AddDbContextPool()` para reduzir a sobrecarga de alocação. - **Aplicação que requer consultas de banco de dados paralelas**: Usar `AddDbContextFactory()` ou `AddPooledDbContextFactory()`. - **Aplicação Blazor Server**: Usar `AddDbContextFactory()` com contextos de curta vida criados por operação. - **Serviços em background**: Usar `IServiceScopeFactory` ou `IDbContextFactory()` para criar contextos dentro do serviço. - **Processamento em lote com paralelismo**: Usar `AddPooledDbContextFactory()` para performance ideal. Para uma cobertura mais profunda de padrões async no ASP.NET Core, o [módulo de programação async](/technologies/dotnet/interview-questions/async-aspnet-core) cobre conceitos relacionados. O [módulo avançado de EF Core](/technologies/dotnet/interview-questions/ef-core-advanced) expande a otimização de queries e o comportamento do change tracking discutidos aqui. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/dotnet/dbcontext-lifetime-performance-thread-safety-async