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

发布于 2024-11-05 02:44:13 字数 1685 浏览 0 评论 0原文

我使用 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);

并且我最终会得到相同的结果。以下是我所在的位置,但我无法在 Expression 中使用 Func 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;
}

我也有兴趣听听这是否是一个坏主意。

I am using Expression.And and Expression.Or to build dynamic linq queries. When the property/field being queried is a string, and the string contains spaces I would like to tokenize the string on the spaces and create an "And'd" sub query on the tokens.

Here is what I mean in a non generic fashion

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);

What I'd like to do is find a way of writing a method which is generic enough to just call for example:

PredicateBuilder.BuildTokenizedStringQuery<Entity>(
                                   tokens, entity => entity.Code);

and I end up with the same result. The following is where I'm at but I can't use the Func stringProp accessor in and Expression. I have to somehow combine an accessor expression (of the string property) with an invocation expression (that invokes 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;
}

I'd also be interested to hear if this all looks like a bad idea.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

鹿港小镇 2024-11-12 02:44:13

这是我用来执行此操作的方法:

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”一词的所有书籍。

Here's what I use to do this:

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);
        }
    }
}

Usage

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();
}

Results

This will find all books whose title contains the words "kill" or "mockingbird."

烟雨凡馨 2024-11-12 02:44:13

乔什提供的答案非常棒,帮助我得到了我想要的东西。然而,它测试每个标记的相等性(它也更通用,因为可以针对任何类型测试相等性),而不是 string.Contains 测试。这是一个给出 string.Contains 结果的解决方案:

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

The answer Josh provided is awesome and helped me to get exactly what I want. It however tests for equality of each token (it is also more generic as equality can be tested against any type) as opposed to a string.Contains test. Here is a solution that gives a string.Contains result:

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文