代码之家  ›  专栏  ›  技术社区  ›  laktak

在表达式树中组合表达式

  •  6
  • laktak  · 技术社区  · 16 年前

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

    例如,如果我想创建这样的表达式树:

    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);
    
    3 回复  |  直到 9 年前
        1
  •  5
  •   Quartermeister    16 年前

    你可以使用 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));
    }
    

    inner表达式.invoke表示表达式 select(x) 外面的那个代表呼叫 y => y.StartsWith(find) 关于返回的值 选择(x) .

    你也可以用 Expression.Call 要在不使用第二个lambda的情况下表示对startswith的调用,请执行以下操作:

    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));
    }
    
        2
  •  3
  •   albertein    16 年前

    这工作:

    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
  •  1
  •   jeroenh    16 年前

    通常情况下,您不会按照描述的方式(使用iqueryable接口)执行此操作,但您更愿意使用类似这样的表达式 Expression<Func<TResult, T>> . 这样,您就可以组成更高阶的函数(例如 where select )输入一个查询并传入表达式,该表达式将“填充”所需的功能。

    例如,考虑 Enumerable.Where 方法:

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

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

    现在,让我们看看 Queryable.Where :

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

    我们可以观察到高阶函数的相同模式,但不是 Func<> 委托它需要一个表达式。表达式基本上是代码的数据表示形式。编译该表达式将为您提供一个真正的(可执行的)委托。编译器要从分配给的lambda构建表达式树,需要做大量的工作 Expression<...> . 表达式树可以针对不同的数据源(如SQL Server数据库)编译所描述的代码。

    回到你的例子,我认为你要找的是 选择器 . 选择器获取每个输入元素并返回其投影。签名如下: 表达式<func<treult,t>> . 例如,您可以指定这一个:

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

    此选择器将允许您将输入字符串投影到所需的类型。我希望我能正确地理解你的意图,很难看到你想要达到的目标。