DbContext is Not Thread-Safe with C# Code Examples – Part 5

awjanthiel

DbContext Is Not Thread-Safe With C# Code Examples – Part 5. Understanding Safe Usage Patterns

The DbContext in Entity Framework Core is a vital class for interacting with your database. However, it is crucial to know that DbContext is not thread-safe. This means that you must avoid using the same instance of DbContext across multiple threads simultaneously to prevent data corruption, exceptions, and unpredictable behaviors.

In this fifth part of our series, we will explore practical C# code examples illustrating why DbContext is not thread-safe and how to safely use it in multithreaded environments.

Why DbContext Is Not Thread-Safe

DbContext manages a lot of internal state such as change tracking and connection handling. When multiple threads attempt to access or modify this shared state concurrently, it leads to race conditions and thread-safety issues. Microsoft’s official documentation advises against sharing a single DbContext instance across threads.

Example of Unsafe Multithreaded Use of DbContext

public void UnsafeParallelOperations()
{
    var context = new MyDbContext();

    var tasks = Enumerable.Range(1, 5).Select(i =>
        Task.Run(() =>
        {
            var data = context.Entities.Find(i);
            Console.WriteLine(data?.Name);
        })).ToArray();

    Task.WaitAll(tasks);
}

This code creates multiple tasks that share the same DbContext instance. Because DbContext is not thread-safe, this approach will likely throw exceptions such as InvalidOperationException or cause data corruption.

Safe Pattern 1. Use DbContext per Thread/Operation

Each thread or asynchronous operation should create and use its own DbContext instance. This isolates state within each context and avoids conflicts.

public async Task SafeParallelOperationsAsync()
{
    var tasks = Enumerable.Range(1, 5).Select(async i =>
    {
        using(var context = new MyDbContext())
        {
            var data = await context.Entities.FindAsync(i);
            Console.WriteLine(data?.Name);
        }
    });

    await Task.WhenAll(tasks);
}

This example ensures each operation uses a fresh DbContext, eliminating thread safety issues.

Safe Pattern 2. Use IDbContextFactory for Dependency Injection

When using dependency injection in ASP.NET Core, leverage IDbContextFactory<T> to create new DbContext instances as needed.

public class MyService
{
    private readonly IDbContextFactory<MyDbContext> _contextFactory;

    public MyService(IDbContextFactory<MyDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public async Task RunParallelQueriesAsync()
    {
        var tasks = Enumerable.Range(1, 5).Select(async i =>
        {
            using var context = _contextFactory.CreateDbContext();
            var entity = await context.Entities.FindAsync(i);
            Console.WriteLine(entity?.Name);
        });

        await Task.WhenAll(tasks);
    }
}

This pattern recommended by Microsoft safely creates isolated DbContext instances for concurrent use.

Summary

  • Do not share a single DbContext instance across multiple threads.
  • Create a new DbContext instance per thread, task, or HTTP request.
  • Use IDbContextFactory when applying dependency injection to generate fresh contexts.
  • Avoid static or long-lived DbContext instances in concurrent scenarios.

Following these safe usage patterns ensures your application avoids threading issues and maintains data integrity when working with Entity Framework Core.

If you want, I can also provide more advanced patterns like async locking or concurrent queueing for DbContext operations. Just ask.