Introduction
When building backend applications using Entity Framework in ASP.NET, developers often encounter a confusing runtime error:
| “A second operation started in this context before a previous operation was completed.” |
At first glance, the code may appear correct and may even work during early development. However, as the application grows and handles more concurrent requests, this issue begins to appear more frequently.
What Is DbContext?
DbContext is the primary class in Entity Framework responsible for:
- Managing database connections
- Tracking entity changes
- Executing queries
- Saving changes to the database
It represents a single unit of work for interacting with the database. Because of this design, DbContext is intended to handle only one operation at a time.
Why DbContext Is Not Thread Safe
Internally, DbContext maintains several components such as change tracking, database connection management, and query execution state.
If multiple threads attempt to use the same instance simultaneously, these internal states may conflict with each other. This can lead to runtime exceptions or inconsistent behavior.
In simple terms, DbContext was designed to work sequentially rather than concurrently.
A Common Scenario That Causes the Problem
A typical mistake occurs when multiple asynchronous tasks share the same DbContext instance.
Problematic Code Example:
| public async Task GetData() { var usersTask = _context.Users.ToListAsync(); var ordersTask = _context.Orders.ToListAsync(); await Task.WhenAll(usersTask, ordersTask); } |
What Happens Here
Both queries attempt to run at the same time using the same DbContext instance. This raises a runtime error:
| “A second operation started in this context before a previous operation was completed.” |
Even though the code appears efficient, the shared context creates a concurrency conflict.
Why This Becomes a Serious Problem
- Runtime Exceptions: Concurrent access can break API responses and generate application errors.
- Data Inconsistency: Multiple threads modifying tracked entities may corrupt the change tracker state.
- Scalability Issues: As traffic increases, concurrent operations become more frequent, making the problem appear more often.
The Correct Approach: Use One DbContext Per Request
The recommended practice in ASP.NET Core is to register the DbContext with a scoped lifetime. This ensures that every HTTP request receives its own instance of DbContext.
Correct Configuration
| services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString)); |
Alternative Fix: Use Separate Context Instances
If parallel operations are required, each task should use its own DbContext instance.
Code Example (Safe Parallel Usage)
| public async Task GetData() { using var context1 = new AppDbContext(); using var context2 = new AppDbContext(); var usersTask = context1.Users.ToListAsync(); var ordersTask = context2.Orders.ToListAsync(); await Task.WhenAll(usersTask, ordersTask); } |
How to Avoid This Issue
Developers can prevent this problem by following a few simple practices:
- Do not share DbContext across multiple threads
- Avoid running parallel queries in the same context
- Use Dependency Injection to manage the DbContext lifecycle
- Treat DbContext as a short-lived unit of work
Conclusion
Many backend reliability issues are not caused by complex algorithms but by incorrect usage of core infrastructure components. Treat DbContext as a short‑lived unit of work, and understanding how DbContext manages state and database operations helps developers build safer and more scalable applications.



