将 IQueryable<T>Where() 扩展为 OR 而不是 AND 关系
-
06-09-2019 - |
题
我使用自己的 IQueryable<> 扩展方法来创建可链接查询,例如 FindAll().FindInZip(12345).NameStartsWith("XYZ").OrderByHowIWantIt() 等。然后在延迟执行时根据我的扩展方法链创建一个查询。
但问题是,扩展链中的所有位置(FindXYZ、FindInZip 等)将始终组合为 AND,这意味着我不能执行以下操作:
FindAll().FirstNameStartsWith("X").OrLastNameStartsWith("Z") 因为我不知道如何在单独的Where方法中注入OR。
知道我该如何解决这个问题吗?
额外的;到目前为止,我了解如何将表达式链接为 Or 如果我将它们包装起来(例如CompileAsOr(FirstNameStartsWith("A").LastNameStartsWith("Z").OrderBy(..))
我想做的事情稍微复杂一些(PredicateBuilder 在这里没有帮助..),因为我希望稍后的 IQueryable 基本上访问之前建立的Where条件,而不必包装它们来创建 Or 之间的条件他们。
当每个扩展方法返回 IQueryable<> 时,我知道它应该了解某处查询条件的当前状态,这使我相信应该有某种自动化的方法或在所有先前的 Where 条件中创建 Or ,而不必换行你想要什么Ord。
解决方案
我假设查询的不同部分在运行时只知道,即你不能只用||
在where
...
一个懒惰选项是Concat
- 但是这往往导致不良的TSQL等;不过,我倾向于倾向于编写自定义Expression
s代替。采取这种方法依赖于供应商是什么,LINQ到SQL支持不同的选项,EF(例如) - 这这里有一个真正的影响(因为你不能使用子表达式与EF)。你能告诉我们哪些?
下面是一些代码应与LINQ到SQL工作;如果你建立一个数组(或列表,并调用.ToArray()
)的表达式,它应该很好地工作;例子是LINQ到对象,但仍应工作:
static void Main()
{
var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();
var predicates = new List<Expression<Func<int, bool>>>();
predicates.Add(i => i % 3 == 0);
predicates.Add(i => i >= 8);
foreach (var item in data.WhereAny(predicates.ToArray()))
{
Console.WriteLine(item);
}
}
public static IQueryable<T> WhereAny<T>(
this IQueryable<T> source,
params Expression<Func<T,bool>>[] predicates)
{
if (source == null) throw new ArgumentNullException("source");
if (predicates == null) throw new ArgumentNullException("predicates");
if (predicates.Length == 0) return source.Where(x => false); // no matches!
if (predicates.Length == 1) return source.Where(predicates[0]); // simple
var param = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Invoke(predicates[0], param);
for (int i = 1; i < predicates.Length; i++)
{
body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
}
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return source.Where(lambda);
}
其他提示
使用 PredicateBuilder<T>
。这也可能是你想要的。
List<string> fruits =
new List<string> { "apple", "passionfruit", "banana", "mango",
"orange", "blueberry", "grape", "strawberry" };
var query = fruits.AsQueryable();
// Get all strings whose length is less than 6.
query = query.Where(fruit => fruit.Length < 6);
// Hope to get others where length is more than 8. But you can't, they're gone.
query = query.Where(fruit => 1 == 1 || fruit.Length > 8);
foreach (string fruit in query)
Console.WriteLine(fruit);
我个人认为在一个理想的世界里 ||
和 &&
运算符将是最简单和可读的。但是它不会编译。
操作员'||'不能应用于类型的操作数'
Expression<Func<YourClass,bool>>
' 和 'Expression<Func<YourClass,bool>>
'
因此我为此使用了扩展方法。在你的例子中它看起来像这样: .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now))
.
代替:
.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now))
.
表达式示例:
private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
return post => post.PostedOn >= cutoffDate;
};
扩展方法:
public static class PredicateExtensions
{
/// <summary>
/// Begin an expression chain
/// </summary>
/// <typeparam id="T""></typeparam>
/// <param id="value"">Default return value if the chanin is ended early</param>
/// <returns>A lambda expression stub</returns>
public static Expression<Func<T, bool>> Begin<T>(bool value = false)
{
if (value)
return parameter => true; //value cannot be used in place of true/false
return parameter => false;
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.AndAlso);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
return CombineLambdas(left, right, ExpressionType.OrElse);
}
#region private
private static Expression<Func<T, bool>> CombineLambdas<T>(this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right, ExpressionType expressionType)
{
//Remove expressions created with Begin<T>()
if (IsExpressionBodyConstant(left))
return (right);
ParameterExpression p = left.Parameters[0];
SubstituteParameterVisitor visitor = new SubstituteParameterVisitor();
visitor.Sub[right.Parameters[0]] = p;
Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
private static bool IsExpressionBodyConstant<T>(Expression<Func<T, bool>> left)
{
return left.Body.NodeType == ExpressionType.Constant;
}
internal class SubstituteParameterVisitor : ExpressionVisitor
{
public Dictionary<Expression, Expression> Sub = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
Expression newValue;
if (Sub.TryGetValue(node, out newValue))
{
return newValue;
}
return node;
}
}
#endregion
}
一篇关于通过扩展表达式进行 LINQ 查询的非常好的文章。也是我使用的扩展方法的来源。