我正在使用Expression.And
和Expression.Or
来构建动态linq查询。当被查询的属性/字段是一个字符串,并且该字符串包含空格时,我想对空格上的字符串进行标记,并在标记上创建“And'd”子查询。
这就是我所说的非通用方式
var tokens = Code.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries);
var index = 0;
var firstToken = tokens[index ++];
Expression<Func<Entity, bool>> subQuery =
entity => entity.Code.Contains(firstToken);
for (; index < tokens.Length; index ++)
{
var tempToken = tokens[index];
subQuery = subQuery.And(entity => entity.Code.Contains(tempToken));
}
query = query.Or(subQuery);
我想要做的是找到一种编写方法的方法,该方法足够通用,只需要调用例如:
PredicateBuilder.BuildTokenizedStringQuery<Entity>(
tokens, entity => entity.Code);
我最终得到了相同的结果。以下是我所在的地方,但我不能在Func
中使用Expression
stringProp访问器。我必须以某种方式将一个访问者表达式(字符串属性)与一个调用表达式(调用string.Contains
)
private Expression<Func<T, bool>> BuildTokenizedStringQuery<T>(string[] tokens,
Func<T, string> stringProp)
{
var index = 0;
var firstToken = tokens[index++];
Expression<Func<T, bool>> subQuery = entity =>
stringProp(entity).Contains(firstToken);
for (; index < tokens.Length; index++)
{
var tempToken = tokens[index];
subQuery = subQuery.And(
entity => stringProp(entity).Contains(tempToken));
}
return subQuery;
}
我也很想知道这一切看起来是个坏主意。
答案 0 :(得分:2)
以下是我用来做这件事的事情:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
namespace MyLibrary.Extensions
{
/// <summary>Defines extension methods for building and working with Expressions.</summary>
public static class ExpressionExtensions
{
/// <summary>Ands the Expressions.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expressions">The Expression(s) to and.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> And<T>(this IEnumerable<Expression<Func<T, bool>>> expressions)
{
if (expressions.IsNullOrEmpty())
return null;
Expression<Func<T, bool>> finalExpression = expressions.First();
foreach (Expression<Func<T, bool>> e in expressions.Skip(1))
finalExpression = finalExpression.And(e);
return finalExpression;
}
/// <summary>Ors the Expressions.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expressions">The Expression(s) to or.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this IEnumerable<Expression<Func<T, bool>>> expressions)
{
if (expressions.IsNullOrEmpty())
return null;
Expression<Func<T, bool>> finalExpression = expressions.First();
foreach (Expression<Func<T, bool>> e in expressions.Skip(1))
finalExpression = finalExpression.Or(e);
return finalExpression;
}
/// <summary>Ands the Expression with the provided Expression.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expression1">The left Expression to and.</param>
/// <param name="expression2">The right Expression to and.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
//Reuse the first expression's parameter
ParameterExpression param = expression1.Parameters.Single();
Expression left = expression1.Body;
Expression right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
BinaryExpression body = Expression.AndAlso(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
/// <summary>Ors the Expression with the provided Expression.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expression1">The left Expression to or.</param>
/// <param name="expression2">The right Expression to or.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
//Reuse the first expression's parameter
ParameterExpression param = expression1.Parameters.Single();
Expression left = expression1.Body;
Expression right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
BinaryExpression body = Expression.OrElse(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
/// <summary>Updates the supplied expression using the appropriate parameter.</summary>
/// <param name="expression">The expression to update.</param>
/// <param name="oldParameter">The original parameter of the expression.</param>
/// <param name="newParameter">The target parameter of the expression.</param>
/// <returns>The updated expression.</returns>
private static Expression RebindParameter(Expression expression, ParameterExpression oldParameter, ParameterExpression newParameter)
{
if (expression == null)
return null;
switch (expression.NodeType)
{
case ExpressionType.Parameter:
{
ParameterExpression parameterExpression = (ParameterExpression)expression;
return (parameterExpression.Name == oldParameter.Name ? newParameter : parameterExpression);
}
case ExpressionType.MemberAccess:
{
MemberExpression memberExpression = (MemberExpression)expression;
return memberExpression.Update(RebindParameter(memberExpression.Expression, oldParameter, newParameter));
}
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
{
BinaryExpression binaryExpression = (BinaryExpression)expression;
return binaryExpression.Update(RebindParameter(binaryExpression.Left, oldParameter, newParameter), binaryExpression.Conversion, RebindParameter(binaryExpression.Right, oldParameter, newParameter));
}
case ExpressionType.Call:
{
MethodCallExpression methodCallExpression = (MethodCallExpression)expression;
return methodCallExpression.Update(RebindParameter(methodCallExpression.Object, oldParameter, newParameter), methodCallExpression.Arguments.Select(arg => RebindParameter(arg, oldParameter, newParameter)));
}
case ExpressionType.Invoke:
{
InvocationExpression invocationExpression = (InvocationExpression)expression;
return invocationExpression.Update(RebindParameter(invocationExpression.Expression, oldParameter, newParameter), invocationExpression.Arguments.Select(arg => RebindParameter(arg, oldParameter, newParameter)));
}
default:
{
return expression;
}
}
}
public static Expression<Func<T, bool>> BuildContainsExpression<T, R>(Expression<Func<T, R>> valueSelector, IEnumerable<R> values)
{
if (null == valueSelector)
throw new ArgumentNullException("valueSelector");
if (null == values)
throw new ArgumentNullException("values");
ParameterExpression parameterExpression = valueSelector.Parameters.Single();
IEnumerable<BinaryExpression> equalExpressions = null;
Expression aggregationExpression = null;
if (!values.IsNullOrEmpty())
return (e => false);
equalExpressions = values.Select(v => Expression.Equal(valueSelector.Body, Expression.Constant(v, typeof(R))));
aggregationExpression = equalExpressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(aggregationExpression, parameterExpression);
}
public static Expression<Func<T, bool>> BuildDoesNotContainExpression<T, R>(Expression<Func<T, R>> valueSelector, IEnumerable<R> values)
{
if (null == valueSelector)
throw new ArgumentNullException("valueSelector");
ParameterExpression parameterExpression = valueSelector.Parameters.Single();
IEnumerable<BinaryExpression> notEqualExpressions = null;
Expression aggregationExpression = null;
if (!values.IsNullOrEmpty())
return (e => false);
notEqualExpressions = values.Select(v => Expression.NotEqual(valueSelector.Body, Expression.Constant(v, typeof(R))));
aggregationExpression = notEqualExpressions.Aggregate<Expression>((accumulate, equal) => Expression.And(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(aggregationExpression, parameterExpression);
}
}
}
string query = "kill mockingbird";
string[] tokens = query.Split(' ');
Expression<Func<Book, string>> inClause = BuildContainsExpression<Book, string>(o => o.Title, tokens);
using (LibraryDataContext dataContext = new LibraryDataContext())
{
List<Book> matchingBooks = dataContext.Books.Where(inClause).ToList();
}
这将找到标题中包含“kill”或“mockingbird”字样的所有书籍。
答案 1 :(得分:0)
Josh提供的答案很棒,帮助我得到了我想要的东西。然而,它测试每个标记的相等性(它也更通用,因为可以针对任何类型测试相等性),而不是字符串。包含测试。这是一个给出字符串的解决方案。包含结果:
public static Expression<Func<T, bool>>
BuildTokenizedStringQuery<T>(string[] tokens,
Expression<Func<T, string>> stringPropertyAccessor)
{
ParameterExpression parameterExpression = stringPropertyAccessor.Parameters
.Single();
var index = 0;
var firstToken = tokens[index ++];
Expression<Func<string, bool>> contains =
aString => aString.Contains(firstToken);
var invocation = Expression.Invoke(contains, stringPropertyAccessor.Body);
Expression<Func<T, bool>> expression = Expression
.Lambda<Func<T, bool>>(invocation, parameterExpression);
for (; index < tokens.Length; index++)
{
var tempToken = tokens[index];
contains = aString => aString.Contains(tempToken);
invocation = Expression.Invoke(contains, stringPropertyAccessor.Body);
expression = expression.And(Expression
.Lambda<Func<T, bool>>(invocation, parameterExpression));
}
return expression;
}