Home > Net >  Where clause in LINQ calling an async method
Where clause in LINQ calling an async method

Time:05-10

I have a method in a class defined as....

public static async Task<int> GetDCountAsync(int dId)

I'm trying to call this method within a LINQ Where clause....

var excessD= dToConsider
    .Where(async x => await myService.GetDCountAsync(x.Id) >= x.Threshold)
    .Select(x => x.Id)
    .ToArray();

FYI: Threshold is an int.

I'm getting an error for async....

The return type of an 'async' anonymous function must be a 'void', 'Task', 'Task', ......

The return type of GetDCountAsync is async Task. Where am I going wrong? Thanks in advance!

CodePudding user response:

So the full error message you are getting will be :

Cannot convert async lambda expression to delegate type 'Func<int, bool>'. An async lambda expression may return void, Task or Task, none of which are convertible to 'Func<int, bool>'.

What it's actually telling you is that a .Where() LINQ call is expecting to take an int, and return a boolean. But because you are trying to do an async call, it's instead trying to return Task.

Or in other words, the compiler is saying "I see you are using an async method here, I know that async methods can only return void, Task or Task, and I also know that this Where method expects a bool back, so I know right now this is not gonna work"

Part of the problem is that we don't actually know what type dToConsider is, and in some cases, it's going to look to do deferred execution. In any case, because you are doing custom code logic for the WHERE statement, let's assume that this is not EntityFramework or something that needs to be deferred and say it's just a list. In that case, simple using a typical ForEach loop to filter your list down.

var filteredItems = new List<T>();
foreach(var item in dToConsider)
{
    if(await myService.GetDCountAsync(x.Id) >= x.Threshold)
        filteredItems.Add(item);
}

I'm sure someone may answer with a nice extension method for you using Task.WhenAll etc, but the this is the simplest way to get what you need done.

CodePudding user response:

You could use the functionality already available in the System.Linq.Async package:

int[] excessD = await dToConsider
    .ToAsyncEnumerable()
    .WhereAwait(async x => await myService.GetDCountAsync(x.Id) >= x.Threshold)
    .Select(x => x.Id)
    .ToArrayAsync();

The signature of the WhereAwait operator:

// Filters the elements of an async-enumerable sequence based on
// an asynchronous predicate.
public static IAsyncEnumerable<TSource> WhereAwait<TSource>(
    this IAsyncEnumerable<TSource> source,
    Func<TSource, ValueTask<bool>> predicate);

The asynchronous method myService.GetDCountAsync will be invoked and awaited for one element at a time, not for all of them concurrently.

  • Related