Using async / await inside .Select lambda

I think you are mixing two things here. Expression trees and delegates. Lambda can be used to express both of them, but it depends on the type of parameter the method accepts in which one it will be turned.

A lambda passed to a method which as Action<T> or Func<T, TResult> will be converted into a delegate (basically an anonymous function/method).

When you pass lambda expression to a method accepting Expression<T>, you create an expression tree from the lambda. Expression trees are just code which describe code, but are not code themselves.

That being said, an expression tree can’t be executed because it’s converted to executable code. You can compile a expression tree at runtime and then execute it like a delegate.

ORM Frameworks use expression trees to allow you writing “code” which can be translated into something different (a database query for example) or to dynamically generate code at runtime.

For that reason you can’t use async in methods that accept Expression<T>. The reason why it may work when you convert it to AsEnumerable() is because it returns an IEnumerable<T> and the LINQ methods on it accept Func<T, TResult>. But it essentially fetches the whole query and does the whole stuff in memory, so you can’t use projections (or you have to fetch the data before using just expressions and projections), turn the filtered result into list and then filter it.

You could try something like this:

// Filter, sort, project it into the view model type and get the data as a list
var users = await allUsers.OrderBy(user => user.FirstName)
                             .Select(user => new UsersViewModel
    {
        Id = user.Id,
        UserName = user.UserName,
        FirstName = user.FirstName,
        LastName = user.LastName,
        DisplayName = user.DisplayName,
        Email = user.Email,
        Enabled = user.Enabled
    }).ToListAsync();

// now that we have the data, we iterate though it and 
// fetch the roles
var userViewModels = users.Select(async user => 
{
    user.Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
});

The first part will be done fully on the database and you keep all your advantages (i.e. the order happens on database, so you don’t have to do in-memory sorting after fetching the results and the limit calls limits the data fetched from the DB etc.).

The second part iterates through the result in memory and fetches the data for each temporary model and finally maps it into the view model.

Leave a Comment