用于构建针对标记化字符串的string.contains的Dynamic Linq查询的通用函数

时间:2011-05-06 00:22:25

标签: linq dynamic-linq

我正在使用Expression.AndExpression.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;
}

我也很想知道这一切看起来是个坏主意。

2 个答案:

答案 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;
}