按子句顺序排列的 LINQ 动态表达式

Posted

技术标签:

【中文标题】按子句顺序排列的 LINQ 动态表达式【英文标题】:LINQ dynamic expression in order by clause 【发布时间】:2021-11-28 10:35:42 【问题描述】:

我有以下疑问:

 product = product.OrderByDescending(d => d.ProductAttributeItem
                            .Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
                            .SelectMany(p => p.AttributeItem.AttributeItemValue)
                            .Any(o => EF.Functions.Like(o.Value, "%apple%") || EF.Functions.Like(o.Value, "%samsung%"))

实际上工作得很好。 product 是一个相当复杂的查询,它包含许多基于输入过滤器的谓词。这里我们对查询的Any() 部分感兴趣,特别是谓词部分——如何动态生成EF.Functions.Like(o.Value, "%apple%") || EF.Functions.Like(o.Value, "%samsung%")

我已经在我们的项目中使用了一些谓词构建器扩展方法,它们非常适用于非嵌套情况,例如:

var condition = PredicateBuilder.True<AttributeItemValue>();

if(filters.OnlyActivated)
     condition = condition.And(product => product.IsActive);

product = _context.Product.Where(condition);

所以我尝试在循环中构建谓词:

var aivCond = PredicateBuilder.True<AttributeItemValue>();
foreach (var s in searchQueryArray)

      aivCond = aivCond.Or(f =>
      EF.Functions.Like(f.Value, "%" + s + "%"));

所以现在aivCond 的类型为Expression&lt;Func&lt;AttributItemValue, bool&gt;,但这不能用于替换Any() 中的lambda,因为它需要Func&lt;TSource, bool&gt;。尝试用这种方式编译aivCond.Compile() 但出现如下错误:

System.ArgumentException: Expression of type 'System.Func`2[AttributeItemValue,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[AttributeItemValue,System.Boolean]]' of method 'Boolean Any[AttributeItemValue](System.Linq.IQueryable`1[AttributeItemValue]

我也尝试过从字符串构建 lambda 表达式:

var filter = "f => EF.Functions.Like(f.Value, \"%apple%\") || f => EF.Functions.Like(f.Value, \"%samsung%\")";

                var options = ScriptOptions.Default
                    .AddReferences(typeof(AttributeItemValue).Assembly)
                    .AddReferences(typeof(Microsoft.EntityFrameworkCore.EF).Assembly)
                    .AddReferences(typeof(DbFunctions).Assembly)
                    .AddImports("Microsoft.EntityFrameworkCore");

                Func<AttributeItemValue, bool> filterExpression = await CSharpScript.EvaluateAsync<Func<AttributeItemValue, bool>>(filter, options);

没有运气。

我知道我缺少表达式树、编译和代表调用方面的知识,因此非常感谢任何帮助(和解释)!

编辑/解决方案

感谢Richard Deeming 的帮助,有一个解决方案。 他以 False 开头谓词是正确的。我愚蠢地从不同的方法复制/粘贴代码而没有注意到。

关于他的第二条评论,添加AsQueryable() 允许传递Expression&lt;Func&lt;TSource, bool&gt;&gt;,这很明显,但对我来说不是。这翻译得很好,编译器没问题。

无论如何,还有另一种方法,使用 LINQKit 的 AsExpandble() 方法和 Expression 类中内置的 Compile() 方法,如 LINQKit 中所述:

Compile 是 Expression 类中的内置方法。它将 Expression 转换为满足编译器的普通 Func 。当然,如果这个方法实际运行,我们最终会得到编译的 IL 代码而不是表达式树,并且 LINQ to SQL 或实体框架会抛出异常。但这是聪明的部分:编译从来没有真正运行过; LINQ to SQL 或实体框架也没有看到它。对 Compile 的调用被一个通过调用 AsExpandable 创建的特殊包装器完全剥离,并替换为正确的表达式树。

所以代码应该是这样的:

product = product.AsExpandable().OrderBy(d => d.ProductAttributeItem
          .Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
          .SelectMany(p => p.AttributeItem.AttributeItemValue)
          .Any(aivCond.Compile()));

感谢大家的帮助,我希望这个问题可以帮助迷失在代码中的人......!

【问题讨论】:

看起来你遇到了相反的问题:PredicateBuilder 正在创建一个普通的Func 而不是Expression。你从哪里得到这门课的? @Charlieface,我从 here 得到它,这是 PredicateBuilder 的早期版本,现在包含在 LINQKit 中 注意: 对于or 条件,您需要以False 而不是True 开头;否则,谓词将始终匹配所有记录。 你试过.SelectMany(p =&gt; p.AttributeItem.AttributeItemValue).AsQueryable().Any(aivCond)吗?那应该让你通过Expression&lt;Func&lt;TSource, bool&gt;&gt; @RichardDeeming,非常感谢您的指导!你真的帮了很多忙!如果您将您的 cmets 添加为答案,我将很乐意接受它:) 【参考方案1】:

如 cmets 中所述,您只需在集合上使用 the AsQueryable method 即可传入 Expression&lt;Func&lt;TItem, bool&gt;&gt; 作为过滤器。

product = product.OrderByDescending(d => d.ProductAttributeItem
    .Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
    .SelectMany(p => p.AttributeItem.AttributeItemValue)
    .AsQueryable().Any(aivCond);

【讨论】:

以上是关于按子句顺序排列的 LINQ 动态表达式的主要内容,如果未能解决你的问题,请参考以下文章

使用查询表达式的 Linq 顺序

Linq学习之旅——LINQ查询表达式

LINQ查询表达式---------let子句

Linq - 按包含顺序排列

如何动态构建LINQ

按子句顺序排列的 PL SQL CASE