ToListAsync(IQueryable)Creates a List<T> from an IQueryable by enumerating it asynchronously.
The rule of thumb is to use async on any thread that by it's busy type waiting, would hamper other threads. For the use of async will have the operations of waiting to step it aside until the data return.
Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
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. Only operators which cause query execution have async counterparts.
The problem seems to be that you have misunderstood how async/await work with Entity Framework.
So, let's look at this code:
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
and example of it usage:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
What happens there?
IQueryable
object (not accessing database yet) using repo.GetAllUrls()
IQueryable
object with specified condition using .Where(u => <condition>
IQueryable
object with specified paging limit using .Take(10)
.ToList()
. Our IQueryable
object is compiled to sql (like select top 10 * from Urls where <condition>
). And database can use indexes, sql server send you only 10 objects from your database (not all billion urls stored in database)Okay, let's look at first code:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
With the same example of usage we got:
await context.Urls.ToListAsync();
.Why async/await is preferred to use? Let's look at this code:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
What happens here?
var stuff1 = ...
userId
var stuff2 = ...
userId
So let's look to an async version of it:
var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));
What happens here?
So good code here:
using System.Data.Entity;
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
public async Task<List<URL>> GetAllUrlsByUser(int userId) {
return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();
}
Note, than you must add using System.Data.Entity
in order to use method ToListAsync()
for IQueryable.
Note, that if you don't need filtering and paging and stuff, you don't need to work with IQueryable
. You can just use await context.Urls.ToListAsync()
and work with materialized List<Url>
.
There is a massive difference in the example you have posted, the first version:
var urls = await context.Urls.ToListAsync();
This is bad, it basically does select * from table
, returns all results into memory and then applies the where
against that in memory collection rather than doing select * from table where...
against the database.
The second method will not actually hit the database until a query is applied to the IQueryable
(probably via a linq .Where().Select()
style operation which will only return the db values which match the query.
If your examples were comparable, the async
version will usually be slightly slower per request as there is more overhead in the state machine which the compiler generates to allow the async
functionality.
However the major difference (and benefit) is that the async
version allows more concurrent requests as it doesn't block the processing thread whilst it is waiting for IO to complete (db query, file access, web request etc).
Long story short,IQueryable
is designed to postpone RUN process and firstly build the expression in conjunction with other IQueryable
expressions, and then interprets and runs the expression as a whole.
But ToList()
method (or a few sort of methods like that), are ment to run the expression instantly "as is".
Your first method (GetAllUrlsAsync
), will run imediately, because it is IQueryable
followed by ToListAsync()
method. hence it runs instantly (asynchronous), and returns a bunch of IEnumerable
s.
Meanwhile your second method (GetAllUrls
), won't get run. Instead, it returns an expression and CALLER of this method is responsible to run the expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With