async/await inside LINQ where clause not working

妖精的绣舞 提交于 2021-02-11 04:31:35

问题


I'm tring to make a database query inside a LINQ statement asynchronous, but I'm running into an error. The code below runs fine with out async/await

    var newEntities = _repositoryMapping.Mapper.Map<List<Entry>>(entries);

    newEntities = newEntities.Where(async e => await !_context.Entries.AnyAsync(c => c.Id == e.Id)).ToList();

Severity Code Description Project File Line Suppression State Error CS4010 Cannot convert async lambda expression to delegate type 'Func<Entry, bool>'. An async lambda expression may return void, Task or Task, none of which are convertible to 'Func<Entry, bool>'

Other than breaking this up into a foreach loop, how can I make this work with async/await?


回答1:


If you care about performance, code should be smarter. You just need to send one query and check what is already present in database.

Prepared extension which can do that in generic way:

newEntities = (await newEntities.FilterExistentAsync(_context.Entries, e => e.Id)).ToList();

Implementation is not so complex

public static class QueryableExtensions
{
    public static async Task<IEnumerable<T>> FilterExistentAsync<T, TProp>(this ICollection<T> items,
        IQueryable<T> dbQuery, Expression<Func<T, TProp>> prop, CancellationToken cancellationToken = default)
    {
        var propGetter = prop.Compile();
        var ids = items.Select(propGetter).ToList();
        var parameter = prop.Parameters[0];

        var predicate = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(TProp) }, Expression.Constant(ids), prop.Body);
        var predicateLambda = Expression.Lambda(predicate, parameter);

        var filtered = Expression.Call(typeof(Queryable), "Where", new[] {typeof(T)}, dbQuery.Expression,
            predicateLambda);

        var selectExpr = Expression.Call(typeof(Queryable), "Select", new[] {typeof(T), typeof(TProp)}, filtered, prop);
        var selectQuery = dbQuery.Provider.CreateQuery<TProp>(selectExpr);

        var existingIds = await selectQuery.ToListAsync(cancellationToken);

        return items.Where(i => !existingIds.Contains(propGetter(i)));
    }
}



回答2:


For the Exception, you can add a extension for IEnumerable to support async

    public static class MyExtensions
    {
        public static async Task<IEnumerable<T>> Where<T>(this IEnumerable<T> source, 
                                                          Func<T, Task<bool>> func)
        {
            var tasks = new List<Task<bool>>();

            foreach (var element in source)
            {
                tasks.Add(func(element));
            }

            var results = await Task.WhenAll<bool>(tasks.ToArray());

            var trueIndex = results.Select((x, index) => new { x, index })
                                   .Where(x => x.x)
                                   .Select(x => x.index).ToList();

            var filterSource = source.Where((x, index) => trueIndex.Contains(index));

            return filterSource;
        }
    }

Then you can use someting like below

 var result = await users.Where(async x => await TestAsync(x));

Full code here https://dotnetfiddle.net/lE2swz



来源:https://stackoverflow.com/questions/65592364/async-await-inside-linq-where-clause-not-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!