【问题标题】:Combining Expressions in an Expression Tree在表达式树中组合表达式
【发布时间】:2010-08-10 18:56:43
【问题描述】:

当部分表达式作为参数传递时,如何构建表达式树?

例如如果我想创建这样的表达式树怎么办:

IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar)
{
  query=query.Where(x => x.Foo.StartsWith(foo));
  return query.Where(x => x.Bar.StartsWith(bar));
}

但通过间接创建它们:

IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar)
{
  query=testAdd(query, x => x.Foo, foo);
  return testAdd(query, x => x.Bar, bar);
}

IQueryable<T> testAdd<T>(IQueryable<T> query, 
  Expression<Func<T, string>> select, string find)
{
  // how can I combine the select expression with StartsWith?
  return query.Where(x => select(x) .. y => y.StartsWith(find));
}

结果:

虽然样本没有多大意义(抱歉,我试图保持简单),但结果如下(感谢 Quartermeister)。

它可以与Linq-to-Sql一起使用来搜索以findText开头或等于findText的字符串。

public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query, 
  Expression<Func<T, string>> selectField, string findText)
{
  Expression<Func<string, bool>> find;
  if (string.IsNullOrEmpty(findText) || findText=="*") return query;

  if (findText.EndsWith("*")) 
    find=x => x.StartsWith(findText.Substring(0, findText.Length-1));
  else
    find=x => x==findText;

  var p=Expression.Parameter(typeof(T), null);
  var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p));

  return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p));
}

例如

var query=context.User;

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName);
query=WhereLikeOrExact(query, x => x.LastName, find.LastName);

【问题讨论】:

    标签: c# lambda expression-trees


    【解决方案1】:

    您可以使用Expression.Invoke 创建一个表示将一个表达式应用于另一个表达式的表达式,并使用Expression.Lambda 为组合表达式创建一个新的 lambda 表达式。像这样的:

    IQueryable<T> testAdd<T>(IQueryable<T> query, 
        Expression<Func<T, string>> select, string find)
    {
        Expression<Func<string, bool>> startsWith = y => y.StartsWith(find);
        var parameter = Expression.Parameter(typeof(T), null);
        return query.Where(
            Expression.Lambda<Func<T, bool>>(
                Expression.Invoke(
                    startsWith,
                    Expression.Invoke(select, parameter)),
                parameter));
    }
    

    内部的 Expression.Invoke 表示表达式select(x),外部的表达式表示在select(x) 返回的值上调用y =&gt; y.StartsWith(find)

    您也可以使用Expression.Call 来表示对 StartsWith 的调用,而无需使用第二个 lambda:

    IQueryable<T> testAdd<T>(IQueryable<T> query,
        Expression<Func<T, string>> select, string find)
    {
        var parameter = Expression.Parameter(typeof(T), null);
        return query.Where(
            Expression.Lambda<Func<T, bool>>(
                Expression.Call(
                    Expression.Invoke(select, parameter),
                    "StartsWith",
                    null,
                    Expression.Constant(find)),
                parameter));
    }
    

    【讨论】:

    • 谢谢,您的第一个答案正是我想要的!
    • 这里需要注意的是,它可以与 LINQ2SQL 和 LINQ2Entities 一起使用,但不适用于 EF-EF,因为众所周知的原因,它没有实现 Expression.Invoke
    【解决方案2】:

    这个作品:

    public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1,
                        Expression<Func<T, string>> Selector2, string data1, string data2)
    {
        return Add(Add(query, Selector1, data1), Selector2, data2);
    }
    
    public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data)
    {
        var row = Expression.Parameter(typeof(T), "row");
        var expression =
            Expression.Call(
                Expression.Invoke(Selector, row),
                "StartsWith", null, Expression.Constant(data, typeof(string))
            );
        var lambda = Expression.Lambda<Func<T, bool>>(expression, row);
        return query.Where(lambda);
    }
    

    你像这样使用它:

    IQueryable<XlUser> query = SomehowInitializeIt();
    query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar");
    

    【讨论】:

      【解决方案3】:

      通常您不会按照您描述的方式执行此操作(使用 IQueryable 接口),而是使用像 Expression&lt;Func&lt;TResult, T&gt;&gt; 这样的表达式。话虽如此,您将高阶函数(例如 whereselect)组合到查询中,并传入将“填充”所需功能的表达式。

      例如,考虑Enumerable.Where方法的签名:

      Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)
      

      该函数将委托作为其在每个元素上调用的第二个参数。您从该委托返回的值指示高阶函数是否应产生当前元素(是否将其包含在结果中)。

      现在,我们来看看Queryable.Where

      Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>)
      

      我们可以观察到高阶函数的相同模式,但它采用的是表达式而不是 Func&lt;&gt; 委托。表达式基本上是代码的数据表示。编译该表达式将为您提供一个真正的(可执行的)委托。编译器做了很多繁重的工作来从您分配给Expression&lt;...&gt; 的 lambda 构建表达式树。表达式树可以针对不同的数据源(例如 SQL Server 数据库)编译所描述的代码。

      回到您的示例,我认为您正在寻找的是 选择器。选择器接受每个输入元素并返回它的投影。它的签名如下所示:Expression&lt;Func&lt;TResult, T&gt;&gt;。例如,您可以指定这个:

      Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string
      

      要传入选择器,您的代码需要如下所示:

      IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find)
      {
        // how can I combine the select expression with StartsWith?
        return query.Select(selector) // IQueryable<string> now
                    .Where(x => x.StartsWith(find));
      }
      

      此选择器允许您将输入字符串投影到所需的类型。我希望我正确地理解了你的意图,很难看出你想要实现什么。

      【讨论】:

        猜你喜欢
        • 2016-08-12
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多