简体   繁体   English

在表达式树中组合表达式

[英]Combining Expressions in an Expression Tree

How can I build an expression tree when parts of the expression are passed as arguments? 当表达式的某些部分作为参数传递时,如何构建表达式树?

Eg what if I wanted to create expression trees like these: 例如,如果我想创建这样的表达式树,该怎么办:

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

but by creating them indirectly: 但通过间接创造它们:

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

Result: 结果:

While the samples didn't make much sense (sorry but I was trying to keep it simple), here's the result (thanks Quartermeister). 虽然样本没有多大意义(抱歉,但我试图保持简单),这是结果(感谢Quartermeister)。

It can be used with Linq-to-Sql to search for a string that starts-with or is equal to the findText. 它可以与Linq-to-Sql一起使用,以搜索以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));
}

eg 例如

var query=context.User;

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

You can use Expression.Invoke to create an expression that represents applying one expression to another, and Expression.Lambda to create a new lambda expression for the combined expression. 您可以使用Expression.Invoke创建表示将一个表达式应用于另一个表达式的表达式,并使用Expression.Lambda为组合表达式创建新的lambda表达式。 Something like this: 像这样的东西:

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

The inner Expression.Invoke represents the expression select(x) and the outer one represents calling y => y.StartsWith(find) on the value returned by select(x) . 内部Expression.Invoke表示表达式select(x) ,外部表示对select(x)返回的值调用y => y.StartsWith(find) select(x)

You could also use Expression.Call to represent the call to StartsWith without using a second lambda: 您也可以使用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));
}

This Works: 这个作品:

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

You use it like: 你使用它像:

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

Usually you don't do that in the way you descirbed (using the IQueryable Interface) but you rather use Expressions like Expression<Func<TResult, T>> . 通常你不会以你所描述的方式(使用IQueryable接口)这样做,而是使用像Expression<Func<TResult, T>>这样的Expression<Func<TResult, T>> Having said that, you compose higher order functions (such as where or select ) into a query and pass in expressions that will "fill in" the desired functionality. 话虽如此,您可以将更高阶的函数(例如whereselect )组合成一个查询,并传入将“填充”所需功能的表达式。

For example, consider the signature of the Enumerable.Where method: 例如,考虑Enumerable.Where方法的签名:

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

The function takes a delegate as its second argument that is called on each element. 该函数将委托作为在每个元素上调用的第二个参数。 The value you return from that delegate indicates to the higher order function if it shall yield the current element (include it in the result or not). 从该委托返回的值表示高阶函数是否应该产生当前元素(包括在结果中)。

Now, let's take a look at Queryable.Where : 现在,我们来看看Queryable.Where

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

We can observe the same pattern of a higher order function, but instead of an Func<> delegate it takes an Expression. 我们可以观察到更高阶函数的相同模式,但是它取代了Func<>委托,它需要一个表达式。 An expression is basically a data representation of your code. 表达式基本上是代码的数据表示。 Compiling that expression will give you a real (executable) delegate. 编译该表达式将为您提供真实(可执行)委托。 The compiler does a lot of heavy lifting to build expression trees from lambdas you assign to Expression<...> . 编译器为你分配给Expression<...> lambda建立表达式树做了很多繁重的工作。 Expression trees make it possible to compile the described code against different data sources, such as a SQL Server Database. 表达式树可以针对不同的数据源(如SQL Server数据库)编译所描述的代码。

To come back to your example, what I think you're looking for is a selector . 回到你的例子,我认为你正在寻找的是一个选择器 A selector takes each input element and returns a projection of it. 选择器获取每个输入元素并返回它的投影。 It's signature looks like this: Expression<Func<TResult, T>> . 它的签名如下: Expression<Func<TResult, T>> For example you could specify this one: 例如,你可以指定这个:

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

To pass in a selector, your code would need to look like this: 要传入选择器,您的代码需要如下所示:

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

This selector would allow you to project the input string to the desired type. 此选择器允许您将输入字符串投影到所需的类型。 I hope I got your intention corrrectly, it's hard to see what you're trying to achieve. 我希望我能够正确地理解你的意图,很难看出你想要实现的目标。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM