Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining Expressions in an Expression Tree

How can I build an expression tree when parts of the expression are passed as arguments?

E.g. 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).

It can be used with Linq-to-Sql to search for a string that starts-with or is equal to the 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));
}

e.g.

var query=context.User;

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName);
query=WhereLikeOrExact(query, x => x.LastName, find.LastName);
like image 240
laktak Avatar asked Aug 10 '10 18:08

laktak


People also ask

How do you combine two expressions?

to combine two expressions or more, put every expression in brackets, and use: *?

What is an expression tree give an example?

Each node in an expression tree is an expression. For example, an expression tree can be used to represent mathematical formula x < y where x, < and y will be represented as an expression and arranged in the tree like structure. Expression tree is an in-memory representation of a lambda expression.

What is the expression tree?

Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y .


3 Answers

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

You could also use Expression.Call to represent the call to StartsWith without using a second 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));
}
like image 84
Quartermeister Avatar answered Sep 22 '22 07:09

Quartermeister


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");
like image 37
albertein Avatar answered Sep 22 '22 07:09

albertein


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>>. 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.

For example, consider the signature of the Enumerable.Where method:

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<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. 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 trees make it possible to compile the described code against different data sources, such as a SQL Server Database.

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>>. 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.

like image 43
Johannes Rudolph Avatar answered Sep 22 '22 07:09

Johannes Rudolph