按子句顺序排列的 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<Func<AttributItemValue, bool>
,但这不能用于替换Any()
中的lambda,因为它需要Func<TSource, bool>
。尝试用这种方式编译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<Func<TSource, bool>>
,这很明显,但对我来说不是。这翻译得很好,编译器没问题。
无论如何,还有另一种方法,使用 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 => p.AttributeItem.AttributeItemValue).AsQueryable().Any(aivCond)
吗?那应该让你通过Expression<Func<TSource, bool>>
。
@RichardDeeming,非常感谢您的指导!你真的帮了很多忙!如果您将您的 cmets 添加为答案,我将很乐意接受它:)
【参考方案1】:
如 cmets 中所述,您只需在集合上使用 the AsQueryable
method 即可传入 Expression<Func<TItem, bool>>
作为过滤器。
product = product.OrderByDescending(d => d.ProductAttributeItem
.Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
.SelectMany(p => p.AttributeItem.AttributeItemValue)
.AsQueryable().Any(aivCond);
【讨论】:
以上是关于按子句顺序排列的 LINQ 动态表达式的主要内容,如果未能解决你的问题,请参考以下文章