Steve Whitaker Steve Whitaker - 1 year ago 130
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 Source

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
      ContactId = uc.Contact.Id,

// LINQ-to-Objects
var lookupTasks = users.Select(async u => new
      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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download