Asynchronous Web Request, EntityFramework, and DI, how does it work?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In ASP.NET applications, asynchronous web requests, Entity Framework, and dependency injection work together through request-scoped object lifetimes and async continuation flow. The main idea is simple: the request begins, the DI container creates scoped services such as DbContext, async I/O frees the thread while waiting, and the same request scope remains valid until the request ends. Most confusion comes from thinking async creates a new request scope or a new DbContext automatically, which it does not.
The Core Pieces
These three concepts play different roles:
- asynchronous web requests avoid blocking a worker thread during I/O
- Entity Framework provides database access through a
DbContext - dependency injection creates and supplies objects such as repositories and contexts
They are related at runtime because an HTTP request commonly resolves a service graph that includes a scoped DbContext, then uses async methods such as ToListAsync() while the request is still in progress.
A Typical Request Flow
A simplified flow looks like this:
- an HTTP request reaches the controller or endpoint
- the DI container resolves the controller and its dependencies
- a scoped
DbContextis created for that request - async EF operations release the thread while waiting on the database
- the request resumes when the awaited work completes
- the scoped services are disposed when the request finishes
The important point is that async changes thread usage, not the meaning of the request scope.
Example with Controller, Service, and DbContext
Here is a small ASP.NET Core example.
This is the common shape of an async web request using DI and EF.
Register the Services with the Right Lifetime
The standard lifetime for DbContext in web apps is scoped.
Scoped means one instance per request by default. That fits EF well because the context tracks entities and should not be shared across unrelated requests.
What Async Actually Changes
When your code awaits a database or HTTP call, the request does not block a thread the whole time. The thread can return to the pool while the I/O is pending, then another thread can continue the request later.
What stays the same:
- the logical request scope
- the scoped services resolved for that request
- the
DbContextlifetime, unless you explicitly create a new one
So async is about efficient waiting, not about starting a fresh dependency graph.
Why DbContext Should Not Be Singleton
One of the easiest ways to break this model is to treat DbContext like a singleton. EF contexts are not thread-safe and are not meant to be shared across concurrent requests.
Correct mental model:
- one request gets one scoped context
- async calls on that request can use that context sequentially
- different requests get different contexts
That is why the DI lifetime matters so much here.
Async All the Way Matters Too
If you call async APIs but then block on them with .Result or .Wait(), you lose most of the benefits and can create deadlocks or throughput problems.
Prefer:
Avoid in request code:
The right pattern is to stay async from controller to service to data access where possible.
Common Pitfalls
- Assuming
awaitcreates a new DI scope or a newDbContext. - Registering
DbContextwith the wrong lifetime, especially singleton. - Mixing async EF methods with blocking
.Resultor.Wait()calls. - Sharing one
DbContextacross multiple concurrent operations manually. - Treating async as a performance trick while ignoring lifetime and threading rules.
Summary
- DI creates scoped services such as
DbContextfor each web request. - Async web requests free threads during I/O, but the logical request scope stays intact.
- Entity Framework works well with async methods such as
ToListAsync()when the context is scoped. - '
DbContextshould not be singleton or shared across unrelated concurrent work.' - The safest design is async all the way with correct DI lifetimes and request-scoped data access.

