0

Related To: Create a Lambda Expression With 3 conditions

Please consider this Code:

from a in myTbl
where a.Address.Contains(strToCheck)
select a

How I can convert this to Expression Tree and write above code with Expressions? Main Problem is converting a.Address.Contains(strToCheck) to Expression Tree.

Edit 1) Address is a string field and strToCheck is a string

Thanks

Arian
  • 12,793
  • 66
  • 176
  • 300
  • What is difference between convert `.Select()` to expression tree and `.Contains()` to expression tree? Nothing. So if you know how to convert `Select` you should know how to convert `Contains`. – George Alexandria Sep 24 '17 at 10:29
  • Thanks. But l'm new to Expression trees and there isn't many and good resouce for it. If you know the answer please help me – Arian Sep 24 '17 at 10:31
  • SO contains many examples how to write expression tree that invokes some method, as example `Any` `Where` and many also. You can check a few examples: [first](https://stackoverflow.com/questions/45887790/access-childrenlist-related-property-in-expression-tree) and [second](https://stackoverflow.com/questions/45765350/create-predicate-with-a-binaryexpression-containing-multiple-parameters). Main idea is that you create `MethodCallExpression` from corresponding `MethodInfo`. By the way, why do you want to convert your example to a `ExpressionTree`? – George Alexandria Sep 24 '17 at 10:39
  • It depends of what is the type of `a.Address` - you either need to call `string.Contains` or `Enumerable.Contains`. – Ivan Stoev Sep 24 '17 at 11:59
  • @George Alexandria please see this: https://stackoverflow.com/questions/45673032/create-generic-abstract-class-using-ef-database-first-approach I should use `T` and I haven't access to `T` properties – Arian Sep 24 '17 at 14:44
  • I understand why you want to convert. So try to convert it to expression by yourself as pointed out in the comments above and edit your question if you still have a few issues. – George Alexandria Sep 24 '17 at 15:19
  • @George Alexandria Thanks but I can't convert it to Expression Tree. I realy don't know from what point I should start. I'm very confused – Arian Sep 24 '17 at 17:14
  • Well, if you really want to get full answer you need to explain what is the `Address`. See comment from ***Ivan Stoev*** – George Alexandria Sep 24 '17 at 17:18
  • @George Alexandria I update my question – Arian Sep 24 '17 at 17:48
  • Please, check this [question](https://stackoverflow.com/questions/46392672/need-help-converting-any-lambda-to-an-expression-tree). I think it will very helpful for you. – George Alexandria Sep 24 '17 at 17:58

2 Answers2

3

a.Address.Contains(strToCheck) represents a call to string.Contains instance method on a.Address instance with strToCheck argument.

The simplest way to build the corresponding expression is to use the following Expression.Call overload:

public static MethodCallExpression Call(
    Expression instance,
    string methodName,
    Type[] typeArguments,
    params Expression[] arguments
)

like this (using the terms from the linked question):

var body = Expression.Call(
    Expression.PropertyOrField(param, "Address"), // instance
    "Contains", // method
    Type.EmptyTypes, // no generic type arguments
    Expression.Constant(strToCheck) // argument
);
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

You did not specify the type of myTbl,
So I have created a simple solution using just a list of objects.

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;

namespace Test
{
    class Program
    {
        static void Main(string[] args) {
            var adresses = FilterByAddress("Address", new List<Person> { new Person { Address = "Address1" }, new Person { Address = "AAAAAA" } });
        }

        public static IEnumerable<Person> FilterByAddress(string strToCheck, List<Person> list) {
            var listParam = Expression.Parameter(typeof(IEnumerable<Person>), "list");
            Expression<Func<Person, bool>> contains = a => a.Address.Contains(strToCheck);
            var select = typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Where") && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);
            var genericMethod = select.MakeGenericMethod(new[] { typeof(Person) });
            var call = Expression.Call(null, genericMethod, new Expression[] { listParam, contains });
            var lambda = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<Person>>>(call, new[] { listParam });

            return lambda.Compile().Invoke(list);
        }
    }

    public class Person
    {
        public string Address { get; set; }
    }
}

If you want to use a filter by predicate you can pass an Expresssion<Func<Person, bool>> as parameter (one line)

    static void Main(string[] args) {
        var strToCheck = "Address";
        var list = new List<Person> { new Person { Address = "Address1" }, new Person { Address = "AAAAAA" } };
        var adresses = FilterByAddress(list, p => p.Address.Contains(strToCheck));
    }

    public static IEnumerable<Person> FilterByAddress(List<Person> list, Expression<Func<Person, bool>> predicateEx) {
        var listParam = Expression.Parameter(typeof(IEnumerable<Person>), "list");
        var select = typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Where") && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);
        var genericMethod = select.MakeGenericMethod(new[] { typeof(Person) });
        var call = Expression.Call(null, genericMethod, new Expression[] { listParam, predicateEx });
        var lambda = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<Person>>>(call, new[] { listParam });

        return lambda.Compile().Invoke(list);
    }

In case you have a very compilicated predicate which spans to multiple lines (an expression tree can be evaluated from a one line lambda) you can use a trick to construct an expression tree out of the predicate Func like this:

    static void Main(string[] args) {
        var strToCheck = "Address";
        Func<Person, bool> predicate = p => {
            return p.Address.Contains(strToCheck);
        };

        var list = new List<Person> { new Person { Address = "Address1" }, new Person { Address = "AAAAAA" } };
        var adresses = FilterByAddress(list, predicate);
    }

    public static IEnumerable<Person> FilterByAddress(List<Person> list, Func<Person, bool> predicate) {
        var listParam = Expression.Parameter(typeof(IEnumerable<Person>), "list");
        Expression<Func<Person, bool>> predicateEx = p => predicate(p);
        var select = typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Where") && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);
        var genericMethod = select.MakeGenericMethod(new[] { typeof(Person) });
        var call = Expression.Call(null, genericMethod, new Expression[] { listParam, predicateEx });
        var lambda = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<Person>>>(call, new[] { listParam });

        return lambda.Compile().Invoke(list);
    }

Using the generic approach to filter a list by predicate

    static void Main(string[] args) {
        var strToCheck = "Address";
        Func<Person, bool> predicate = p => {
            return p.Address.Contains(strToCheck);
        };

        var list = new List<Person> { new Person { Address = "Address1" }, new Person { Address = "AAAAAA" } };
        var adresses = FilterBy<Person>(list, predicate);
    }

    public static IEnumerable<T> FilterBy<T>(List<T> list, Func<T, bool> predicate) {
        var listParam = Expression.Parameter(typeof(IEnumerable<T>), "list");
        Expression<Func<T, bool>> predicateEx = p => predicate(p);
        var select = typeof(Enumerable).GetMethods().Single(m => m.Name.Equals("Where") && m.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);
        var genericMethod = select.MakeGenericMethod(new[] { typeof(T) });
        var call = Expression.Call(null, genericMethod, new Expression[] { listParam, predicateEx });
        var lambda = Expression.Lambda<Func<IEnumerable<T>, IEnumerable<T>>>(call, new[] { listParam });

        return lambda.Compile().Invoke(list);
    }
}
Sagi
  • 8,972
  • 3
  • 33
  • 41
  • 2
    You call that a **simple** solution? Some explanation of the used code would be nice. – Flater Sep 25 '17 at 09:50
  • Thanks I will test it. Please consider @Flater comment – Arian Sep 25 '17 at 10:17
  • This line: `Expression> contains = a => a.Address.Contains(strToCheck);` I want to implement this code with parameter. I haven't access to `Address` property because I used generic abstract class and my entity is just `T` – Arian Sep 25 '17 at 10:46
  • Another consideration is I want pass `Expression>` to a function that filter data in database level. – Arian Sep 25 '17 at 10:47