Using async with Entity Framework select list of type IQueryable<T>

You have to be aware between the difference of a query, and the result of the query. An IQueryable holds everything to perform the query. It isn't the query itself, and creating an IQueryable doesn't perform the query.

If you look more closely to LINQ statements, you'll see that there are two types: the ones that return IQueryable (and IEnumerable), and the ones that return List<TResult>, TResults, TKey, etc, anything that are not IQueryable/IEnumerable. If the return value is an IQueryable, then we say that the function uses delayed execution (or lazy execution): the Expression to perform the query is created, but the query is not executed yet.

This has the advantage that you can concatenate LINQ statements, without executing a query per statement.

The query is executed when you ask the IQueryable to get an enumerator and if you start enumerating, either implicitly by using foreach, or explicitly by using IQueryable.GetEnumerator() and IEnumerator.MoveNext() (which are also called by foreach).

So as long as you are creating a query and returning an IQueryable, it is useless to create a Task. Concatenating LINQ statement will only change the Expression of the IQueryable, which is not something that you have to wait for.

Only if you create a function that will actually execute the query you'll need an async version: ToListAsync, FirstOrDefaultAsync, MaxAsync, etc. Internally these functions will GetEnumerator and MoveNextAsync <-- that is the actual async function

Conclusion: all your functions that would normally return IQueryable<...> don't need an Async version , all functions that return actual fetched data need an Async version

Examples. No async needed: no query executed:

// Query customer addresses:
static IQueryable<Address> QueryAddresses(this IQueryable<Customer> customers)
{
     return customers.Select(customer => customer.Address);
}

async needed:

static async Task<List<Address>> FetchAddressesAsync (this IQueryable<Customer> customers)
{
     var query = customers.QueryAddresses;   // no query executed yet
     return await query.ToListAsync();       // execute the query
     // could of course be done in one statement
}

static async Task<Address> FetchAddressAsync(this.IQueryable<Customer> customers, int customerId)
{
    var query = customers.Where(customer => customer.Id == customerId)
                         .QueryAddresses();
    // no query executed yet!
    // execute:
    return await query.FirstOrDefaultAsync();
}

Usage:

int customerId = ...
using (var dbContext = new InvoiceContext())
{
     Address fetchedCustomerAddress = await dbContext.Customers
         .FetchAddressAsync(customerId);
}

In the rare case that you'll have to enumerate yourself, you'll await in MoveNextAsync:

IQueryable<Customer> myCustomers = ...
IEnumerator<Customer> customerEnumerator = myCustomers.GetEnumerator();

while (await customerEnumerator.MoveNextAsync())
{
     Customer customer = customerEnumerator.Current;
     Process(customer);
}