LINQ where 子句中的 async/await 不起作用

Posted

技术标签:

【中文标题】LINQ where 子句中的 async/await 不起作用【英文标题】:async/await inside LINQ where clause not working 【发布时间】:2021-04-11 23:57:44 【问题描述】:

我正在尝试在 LINQ 语句中进行异步数据库查询,但遇到了错误。下面的代码在没有 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();

严重性代码描述项目文件行抑制状态 错误 CS4010 无法将异步 lambda 表达式转换为委托类型 '功能'。异步 lambda 表达式可能会返回 void,Task 或 Task,它们都不能转换为 '功能'

除了把它分解成一个 foreach 循环之外,我怎样才能使它与 async/await 一起工作?

【问题讨论】:

让这个异步 lamda 工作并多次访问数据库可能是错误的方法。让它与一个逻辑 sql 翻译一起工作将是更好的方法。您要做什么,只需检查给定实体的 ID 是否存在?您想在这里解决更大的问题吗? @Qbertsuit?为什么不简单地进行左连接(如果为 null)? @00110001 我只是想将 async/await 添加到一些现有代码中。目前它包含在一个没有意义的 Task.Run 中。我认为代码的原始作者只是想从数据库中已经存在的 newEntries 中删除任何项目 @PeterCsala 你能告诉我怎么做吗? 检查这些:1, 2 【参考方案1】:

如果您关心性能,代码应该更智能。您只需要发送一个查询并检查数据库中已经存在的内容。

准备好的扩展,可以以通用方式做到这一点:

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

实现并不复杂

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】:

对于Exception,您可以为IEnumerable添加扩展以支持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;
        
    

然后你可以使用下面的东西

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

完整代码在这里https://dotnetfiddle.net/lE2swz

【讨论】:

这似乎非常低效。如果source 包含大量项目,假设n 那么这将执行n 到数据库的往返次数以检查存在。 这是一个不错的解决方案,但我同意@PeterCsala。非常感谢您的意见! 这个解决方案通常很复杂O(n^2),但可以用O(n)实现,只需使用sourceresult上的循环而不是trueIndex.Contains(index) @PeterCsala 是的,你是对的,所以我说的只是例外

以上是关于LINQ where 子句中的 async/await 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

linq 到实体,where 子句中的 where ? (内凡)

Java 等效于 C# Linq 中的 Where 子句

使用 Linq 的 Where 子句中的子字符串

Linq to Entities 中的动态 where 子句 (OR)

LINQ 实体 Where 子句不在正确位置

LINQ 中的更新查询包含 WHERE 子句中的所有列,而不仅仅是主键列