Spots

Async LINQ: Why ToListAsync Exists (And Why WhereAsync Doesn't)

You write await dbContext.Products.ToListAsync(). Good. Then you wonder: where's WhereAsync? Why isn't await dbContext.Products.WhereAsync(p => p.Price > 100)? The answer reveals something fundamental about how LINQ actually works. Where the Async Boundary Lives LINQ operators like Where, Select, OrderBy don't execute anything. They build a query. An expression tree. A recipe.

The execution happens at the terminal operator: ToList

The execution happens at the terminal operator: ToList, First, Count, Any. That's when bytes flow over the network. That's when you want async. There's no WhereAsync because Where doesn't touch the database. Making it async would be meaningless. The Async Terminal Operators EF Core provides async versions of all execution-triggering operators: Each one triggers actual database communication. Each one should be awaited. The Streaming Async Pattern ToListAsync waits for all rows before returning. For large result sets, you might want to process as you receive:

AsAsyncEnumerable() returns an IAsyncEnumerable<T> — each iteration…

AsAsyncEnumerable() returns an IAsyncEnumerable<T> — each iteration asynchronously fetches the next item. You don't hold the entire result set in memory.

Fun fact: IAsyncEnumerable<T> was added in C# 8

Fun fact: IAsyncEnumerable<T> was added in C# 8 (2019), but the concept of async iteration goes back to Rx (Reactive Extensions) from 2009. Rx's IObservable<T> pushed data to you; IAsyncEnumerable<T> lets you pull. Ten years from push to pull. The ConfigureAwait Question In library code, you often see:

ConfigureAwait(false) tells the await not to capture the

ConfigureAwait(false) tells the await not to capture the synchronization context. In ASP.NET Core, this doesn't matter (there's no sync context). In desktop/UI apps or older ASP.NET, it prevents deadlocks and improves performance. Application code? Skip ConfigureAwait Library code? Add ConfigureAwait(false) everywhere One sync call in an async method can negate the benefits. Under load, thread pool starvation follows.

Task.Run offloads to a thread pool thread. You're

Task.Run offloads to a thread pool thread. You're still blocking a thread, just a different one. True async (ToListAsync) releases the thread during I/O. DbContext isn't thread-safe. For parallel queries, use separate contexts: All async operators accept cancellation: When the token cancels, EF aborts the database command. Always wire cancellation through from your ASP.NET controller: The Async LINQ to Objects Gap Standard LINQ to Objects (in-memory) doesn't have async operators. If you're filtering an in-memory list, it's just:

No await needed — no I/O involved. The

No await needed — no I/O involved. The async variants are for I/O-bound operations (database, file, network), not CPU-bound in-memory work. Query building (Where, Select) — no async needed Query execution (ToList, First, Count) — use async versions Large results? AsAsyncEnumerable() for streaming Parallel queries? Separate DbContext per task Always pass CancellationToken to async EF operations

Next time, we'll explore LINQ's aggregate operators

Next time, we'll explore LINQ's aggregate operators — Sum, Average, Max, and the lesser-known Aggregate that lets you define custom aggregations. Hope to see you! For further actions, you may consider blocking this person and/or reporting abuse

News

Async LINQ: Why ToListAsync Exists (And Why WhereAsync Doesn't)

You write await dbContext.Products.ToListAsync().

@spots #dev
Source: Dev.to
See more like this