2

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?

Community
  • 1
  • 1
RNDThoughts
  • 892
  • 3
  • 13
  • 30

2 Answers2

7

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I think you may have meant to use `return await Task.WhenAll(lookupTasks);` Not `return await Task.WhenAll(tasks);` – Metro Smurf Oct 20 '16 at 18:21
  • Right, got it. It didnt occur to me to reselect all the properties into a new dynamic property as well :) – RNDThoughts Oct 24 '16 at 09:38
  • If i wanted to just return a property of the object i get back from GetUserByIdAsync how do i access it (.Result.DisplayName)? I get warned there is no GetAwaiter definition? – RNDThoughts Oct 24 '16 at 09:43
  • @SteveWhitaker: You'd probably have to put parenthesis around `await`, like `(await GetUserByIdAsync(id)).DisplayName`. – Stephen Cleary Oct 24 '16 at 13:04
0

I resolved this by creating a wrapper:

    private async Task<string> GetDisplayName(string userId)
    {
        if (string.IsNullOrEmpty(userId)) return null;
        var contactUser = await _graphService.GetUserByIdAsync(userId);
        return contactUser.DisplayName;
    }

and changing the assignment to this:

    Name = u.UserId != null ? await GetDisplayName(u.UserId) : null
RNDThoughts
  • 892
  • 3
  • 13
  • 30