iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
In Entity Framework Core, Where does not run a database query. It adds a condition to an IQueryable<T> expression, and the query runs only when results are consumed. ToListAsync exists because it is that consuming step: it asynchronously executes the composed query and returns the results as a List<T>. Since Where performs no I/O, there is nothing to await, so EF Core has no WhereAsync.
What Where actually does
When you write a LINQ query against a DbContext set, EF Core receives an expression tree describing the query. Each operator such as Where or OrderBy extends that description. The provider later translates the finished description into the database’s query language, such as SQL for SQL Server, and sends it when results are enumerated.
var blogs = await context.Blogs
.Where(b => b.Rating > 3)
.ToListAsync();
Read the statement in two parts. context.Blogs.Where(...) returns another IQueryable<Blog> and makes no database call. The database round trip begins at ToListAsync(), the terminal operation. The same deferred behavior applies to other terminal operations, including ToList, Single, and Count, along with their async counterparts such as SingleAsync and CountAsync.
This has a practical consequence: an IQueryable<T> is a description of work, not a collection of rows. Enumerating the same queryable twice means the query is sent twice.
#1 Best Overall
Why there is no WhereAsync
An async method earns its name when it waits on I/O. Microsoft Learn’s EF Core asynchronous programming guidance addresses this directly:
“Note that there are no async versions of some LINQ operators such as Where or OrderBy, because these only build up the LINQ expression tree and don’t cause the query to be executed in the database.”
Calling Where only records a predicate. There is no network call to wait for, so an asynchronous overload would wrap pure in-memory work in a Task without gaining anything. The I/O is deferred to the terminal operation, and that is where EF Core provides async methods.
Do not carry this model over to LINQ to Objects. On an IEnumerable<T> in memory, Where is also lazy, but there is no database involved and no I/O to await. The rule that async variants appear where I/O happens holds in both cases; the difference is only what the I/O is. The execution semantics described here are EF Core’s provider-backed query behavior.
What ToListAsync does
The API reference for EF Core 10.0 describes ToListAsync as an extension method on IQueryable<TSource> that returns Task<List<TSource>>. It accepts an optional CancellationToken and lives in the Microsoft.EntityFrameworkCore namespace. Its documented summary is: “Asynchronously creates a List<T> from an IQueryable<T> by enumerating it asynchronously.”
The list itself is not the reason for the async signature. Building a list in memory is cheap. The reason is that enumerating the query requires a database round trip, and await lets the calling thread do other work while that I/O completes instead of blocking it.
Rank #4
using Microsoft.EntityFrameworkCore;
public async Task<List<Blog>> GetHighRatedBlogsAsync(CancellationToken cancellationToken)
{
return await context.Blogs
.Where(b => b.Rating > 3)
.OrderBy(b => b.Name)
.ToListAsync(cancellationToken);
}
Where each operation runs
The table below separates the three stages that matter when you choose an async pattern.
Free tools Windows power users keep installed
One-click scans. No signup required.
| Operation | When it does work | Async form | Result |
|---|---|---|---|
Where, OrderBy on IQueryable<T> |
Only records the expression; no database call | None; EF Core does not provide one | IQueryable<T> |
ToListAsync |
Executes the composed query and reads all rows | ToListAsync, returns Task<List<T>> |
Entire result buffered in memory |
AsAsyncEnumerable |
Executes the query and streams rows as they are read | Consumed with await foreach |
IAsyncEnumerable<T> |
Where after AsAsyncEnumerable |
Client-side, against rows already returned | Not a database operation; it no longer reaches SQL | Client-side filtering of fetched rows |
Buffering versus streaming
Choosing between ToListAsync and AsAsyncEnumerable is mainly a memory decision. ToListAsync materializes the full result set, so the application holds every row at once. Microsoft’s efficient-querying guidance contrasts this with AsAsyncEnumerable and await foreach, which process rows one at a time. For large results, streaming keeps application memory from growing with the total row count.
Best Value
await foreach (var blog in context.Blogs
.Where(b => b.Rating > 3)
.AsAsyncEnumerable()
.WithCancellation(cancellationToken))
{
await ProcessAsync(blog);
}
Buffering is the right choice when the result is small, when the caller needs a real List<T>, or when the same results will be enumerated more than once. Avoid calling ToListAsync only to apply one more filter in memory. If that filter can be composed into the query, let the database do it; if it cannot, stream the rows instead of buffering them first. Result size should also be bounded by limiting the query or paging it.
Filtering on the client
Some logic cannot be translated to SQL, such as a call to a method you wrote in C#. The pattern is to put translatable predicates and projections on the query first, then cross the execution boundary with AsAsyncEnumerable, and apply the C# logic to the rows that come back.
var candidates = context.Blogs
.Where(b => b.Rating > 3) // translated to SQL
.AsAsyncEnumerable(); // execution boundary
await foreach (var blog in candidates)
{
if (IsEligible(blog)) // client-side, your own method
{
Console.WriteLine(blog.Name);
}
}
Microsoft’s async guidance notes that LINQ operators for IAsyncEnumerable<T> are being introduced in .NET 10. On earlier target frameworks, the guidance points to the System.Linq.Async package. Do not assume every ordinary Where overload is available on IAsyncEnumerable<T> in every target framework; check the framework you build against.
The same boundary applies to client evaluation. EF Core can evaluate some untranslatable expressions in a top-level projection on the client. Other client-side processing should be made explicit with AsAsyncEnumerable or ToListAsync, so that it is clear where the database stops and the application starts.
Pitfalls to avoid
- Parallel operations on one DbContext. EF Core does not support multiple simultaneous operations on a single context instance. Await each async query before starting the next one on that same context. Starting a second operation early produces an
InvalidOperationException. - Cancellation is not guaranteed to stop the server. The cancellation token passed to
ToListAsyncis observed while the task is awaited, and EF Core passes it to the underlying provider. Whether the provider actually cancels the command depends on the provider. - Unbounded buffering. A
ToListAsyncon a large table holds every row in memory. Add a filter, a limit, or paging, or stream withAsAsyncEnumerable. - Provider-specific async issues. EF Core’s async documentation warns of known issues in the async implementation of Microsoft.Data.SqlClient. If you see unexpected performance problems, particularly with large text or binary values, investigate synchronous command execution as a diagnostic step. This is a provider caveat, not a reason to avoid async EF Core in general.
Sources and version scope
The explanation of async operators and I/O comes from Microsoft Learn’s EF Core asynchronous programming guidance. The ToListAsync signature and description come from the API reference for EF Core 10.0. The buffering and streaming trade-offs come from Microsoft Learn’s efficient-querying guidance, and the deferred execution model comes from its query-processing documentation. Check these pages for the EF Core version you use, since client-evaluation rules and provider behavior have changed across releases.
Quick Recap
“
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

