Steve Whitaker Steve Whitaker - 1 month ago 13
C# Question

How to await during Select in a lambda

I am attempting to perform a Lambda Select using Entity Framework from a table into a new model but I need to be able to call an asynchronous method to populate a property on each instance in the return set:

await Task.WhenAll(_context.UserContacts.Where(uc => uc.UserId == user.Id).Select(async uc => new MailContact
{
Email = uc.Contact.Email,
UserId = uc.Contact.UserId,
ContactId = uc.Contact.Id,
Name = uc.Contact.UserId != null ? await _graphService.GetUserByIdAsync(uc.Contact.UserId) : null;
}


I understand that Linq has limited support for await/async and I've looked at several other examples on StackOverflow where the asynchronous part is moved into a seperate loop, eg:

How to await a method in a Linq query

T[] data = await Task.WhenAll(contacts.Select(c => LoadDataAsync(c)));


However, this method wont let me update the object referred to as "c" unless I pass by reference which isn't allowed on async methods.

Can someone explain the most efficient way to populate the name property correctly?

Answer

You just have to do the secondary lookup in LINQ-to-Objects, not LINQ-to-Entities:

// LINQ-to-Entities
var users = await _context.UserContacts
    .Where(uc => uc.UserId == user.Id)
    .Select(uc => new
    {
      uc.Contact.Email,
      uc.Contact.UserId,
      ContactId = uc.Contact.Id,
    })
    .ToListAsync();

// LINQ-to-Objects
var lookupTasks = users.Select(async u => new
    {
      u.Email,
      u.UserId,
      u.ContactId,
      Name = u.UserId != null ? await _graphService.GetUserByIdAsync(u.UserId) : null;
    });
return await Task.WhenAll(lookupTasks);

The idea is you push as much logic into the initial LINQ-to-Entities query, including filtering and projection. Then you execute it (ToListAsync).

Then you take your DB results and do the secondary lookup.