【问题标题】:Generic Expression tree with 'OR' clause for each supplied property每个提供的属性都带有“OR”子句的通用表达式树
【发布时间】:2013-03-21 21:25:22
【问题描述】:

我为 IQueryable 创建了一个通用搜索扩展方法,使您能够搜索单个属性以查看其中是否包含搜索词。

http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable

我现在想让用户选择多个属性以在每个属性中进行搜索,匹配 any 属性是否包含文本。

代码:

用户输入以下代码来执行此搜索:

string searchTerm = "Essex";
context.Clubs.Search(searchTerm, club => club.Name, club => club.County)

//Note: If possible I would rather something closer to the following syntax...
context.Clubs.Search(club => new[]{ club.Name, club.County}, searchTerm);
// ... or, even better, something similar to this...
context.Clubs.Search(club => new { club.Name, club.County}, searchTerm);

这将返回名称中或县中带有“埃塞克斯”的任何高尔夫球杆。

    public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
    {
        if (String.IsNullOrEmpty(searchTerm))
        {
            return source;
        }

        // The lamda I would like to reproduce:
        // source.Where(x => x.[property1].Contains(searchTerm)
        //                || x.[property2].Contains(searchTerm)
        //                || x.[property3].Contains(searchTerm)...)

        //Create expression to represent x.[property1].Contains(searchTerm)
        var searchTermExpression = Expression.Constant(searchTerm);


        //Build parameters
        var parameters = stringProperties.SelectMany(prop => prop.Parameters);
        Expression orExpression = null;

        //Build a contains expression for each property
        foreach (var stringProperty in stringProperties)
        {
            var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
            if (orExpression == null)
            {
                orExpression = checkContainsExpression;
            }

            //Build or expression for each property
            orExpression = Expression.OrElse(orExpression, checkContainsExpression);
        }

        var methodCallExpression = Expression.Call(typeof(Queryable),
                                                   "Where",
                                                   new Type[] { source.ElementType },
                                                   source.Expression,
                                                   Expression.Lambda<Func<TSource, bool>>(orExpression, parameters));

        return source.Provider.CreateQuery<TSource>(methodCallExpression);
    }

错误

如果我将提供的参数数量更改为 1:

Expression.Lambda<Func<TSource, bool>>(orExpression, parameters.First()));

我收到一个新错误:

更新

我写了a post on the work discussed in this question。 Check it out on GitHub 也是。

【问题讨论】:

  • 要做到这一点,您将不必重新编写树,将“其他” lambda 中的参数替换为您想要用作主控的参数。 ExpressionVisitor 是执行此操作的工具 - SwapExpressionVisitor 非常容易编写(或使用此处的 stackoverflow.com/a/15293475/23354)
  • 更改方法签名以便只传入一个 lambda 会更容易吗?即.Search(club =&gt; new { club.Name, club.County}, searchTerm); 还是坚持使用 params 实现?
  • 这应该不是问题。使用 ExpressionVisitor 实际上很容易 - 抱歉,我不在 IDE 中
  • 我会坚持使用params 方式。然后,您将获得一组 Expression 实例,而不是一个 Expression,其中包含您必须解析的数组。
  • @MarcGravell:好的,感谢 ExpressionVisitor 指针...我会检查一下

标签: c# generics lambda expression-trees


【解决方案1】:

我们来了;你非常接近 - 正如我在 cmets 中指出的那样,这里的关键是使用 ExpressionVisitor 根据你想要保留的单个参数重新编写树:

using System;
using System.Linq;
using System.Linq.Expressions;
static class Program
{
    static void Main()
    {
        var data = new[] { new Foo { A = "x1", B = "y1", C = "y1" }, new Foo { A = "y2", B = "y2", C = "y2" },
            new Foo { A = "y3", B = "y3", C = "x3" } }.AsQueryable();

        var result = data.Search("x", x => x.A, x => x.B, x => x.C);

        foreach (var row in result)
        {
            Console.WriteLine("{0}, {1}, {2}", row.A, row.B, row.C);
        }
    }
    class Foo
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
    }
    public class SwapVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public SwapVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
        public static Expression Swap(Expression body, Expression from, Expression to)
        {
            return new SwapVisitor(from, to).Visit(body);
        }
    }
    public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
    {
        if (String.IsNullOrEmpty(searchTerm))
        {
            return source;
        }
        if (stringProperties.Length == 0) return source.Where(x => false);


        // The lamda I would like to reproduce:
        // source.Where(x => x.[property1].Contains(searchTerm)
        //                || x.[property2].Contains(searchTerm)
        //                || x.[property3].Contains(searchTerm)...)

        //Create expression to represent x.[property1].Contains(searchTerm)
        var searchTermExpression = Expression.Constant(searchTerm);


        var param = stringProperties[0].Parameters.Single();
        Expression orExpression = null;

        //Build a contains expression for each property
        foreach (var stringProperty in stringProperties)
        {
            // re-write the property using the param we want to keep
            var body = SwapVisitor.Swap(stringProperty.Body, stringProperty.Parameters.Single(), param);

            var checkContainsExpression = Expression.Call(
                body, typeof(string).GetMethod("Contains"), searchTermExpression);

            if (orExpression == null)
            {
                orExpression = checkContainsExpression;
            }
            else
            {   // compose
                orExpression = Expression.OrElse(orExpression, checkContainsExpression);
            }
        }

        var lambda = Expression.Lambda<Func<TSource, bool>>(orExpression, param);
        return source.Where(lambda);
    }
}

【讨论】:

  • 感谢 Marc,昨晚您将我指向 ExptessionVisitor 后,确实设法解决了问题。感谢您花时间为我指明正确的方向,今天早上再次提供完整的答案。
  • @NinjaNye 没问题;我没有看到任何更新,所以我认为它仍在等待中:)
  • 当我破解它的时候已经太晚了,而且不想锻炼你的代表,所以我想我会让你有这个荣誉
  • @NinjaNye 请注意这里的最后两行与您所拥有的非常不同 - 如果您仍在使用复杂版本,您可能需要简化它
  • 好声音,我错过了
猜你喜欢
  • 1970-01-01
  • 2023-04-06
  • 2018-09-24
  • 1970-01-01
  • 1970-01-01
  • 2014-01-13
  • 1970-01-01
  • 2012-12-08
  • 2011-07-16
相关资源
最近更新 更多