5

We have an entity of type T1 which has a member of type T. something like this :

public class T1
{
    public T Member{get;set;}
}

User can use our UI to give us a filter over T and we have translate it to an expression of a function that gets a T and returns bool (Expression<Func<T,bool>>)

I would like to know is it possible to convert this to an expression of a function that gets T1 and returns bool.

Actually I'd like to convert this :

(t=>t.Member1==someValue && t.Member2==someOtherValue);

to this :

(t1=>t1.Member.Member1==someValue && t1.Member.Member2==someOtherValue);
Beatles1692
  • 5,214
  • 34
  • 65
  • I'm assuming that none of this information is statically available to the compiler. I understand that the T->bool expression is dynamically provided but is T1 and the Member statically available? And do you still need to have it as an expression or could you convert it to a delegate? – Lasse V. Karlsen Apr 27 '15 at 10:50
  • you can try [_Invoke_](https://msdn.microsoft.com/en-us//library/bb355170(v=vs.110).aspx) – Grundy Apr 27 '15 at 10:53
  • Since I have to pass it to a Linq Provider and I have to work with IQueryables I think that I have to have them as expressions ? – Beatles1692 Apr 27 '15 at 10:53
  • @Grundy something says that answer lies in Invoke but I still can not see how I should use it :) – Beatles1692 Apr 27 '15 at 10:56
  • `AsExpandable` can do that: http://www.albahari.com/nutshell/linqkit.aspx – usr Apr 27 '15 at 10:57
  • @Beatles1692 something like: `var exprT1 = Expression.Invoke(exprT, Expression.PropertyOrField(Expression.Parameter(typeof(T1), "Member"))` – Grundy Apr 27 '15 at 10:59
  • @Grundy can you post it as an answer please :) – Beatles1692 Apr 27 '15 at 11:02
  • just a minute, i provide answer with ExpressionVisitor too :-) – Grundy Apr 27 '15 at 11:15
  • methinks @xanatos answer is more useful :-) – Grundy Apr 27 '15 at 11:34
  • 1
    Hehe, i like posting my answer to other questions, i think this could help you out too :P http://stackoverflow.com/questions/29448432/pass-expression-parameter-as-argument-to-another-expression/29471092#29471092 – MBoros Apr 30 '15 at 00:15

2 Answers2

3

Given

public class MyClass
{
    public MyInner Member { get; set; }
}

public class MyInner
{
    public string Member1 { get; set; }
    public string Member2 { get; set; }
}

plus

public static Expression<Func<TOuter, bool>> Replace<TOuter, TInner>(Expression<Func<TInner, bool>> exp, Expression<Func<TOuter, TInner>> outerToInner)
{
    var body2 = new ExpressionReplacer { From = exp.Parameters[0], To = outerToInner.Body }.Visit(exp.Body);
    var lambda2 = Expression.Lambda<Func<TOuter, bool>>(body2, outerToInner.Parameters);
    return lambda2;
}

and

public class ExpressionReplacer : ExpressionVisitor
{
    public Expression From;
    public Expression To;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == From)
        {
            return base.Visit(To);
        }

        return base.VisitParameter(node);
    }
}

you can

// The initial "data"
string someValue = "Foo";
string someOtherValue = "Bar";
Expression<Func<MyInner, bool>> exp = t => t.Member1 == someValue && t.Member2 == someOtherValue;
Expression<Func<MyClass, MyInner>> outerToInner = u => u.Member;

// The "new" expression
Expression<Func<MyClass, bool>> result = Replace(exp, outerToInner);

The ExpressionReplacer class replaces a parameter of an expression with another expression, while the Replace method uses the ExpressionReplacer and then rebuilds a new expression.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • I have implemented the ExpressionReplacer and wrote a test and it works fine but when I am using it in our project it has a strange behavior.It only replaces the first appearance of T into T1.T . – Beatles1692 Apr 27 '15 at 13:50
  • it replaces `{x => ((x.RealEstateStatus == Advertised) AndAlso (((x.Longitude >= 51.37413024902344) AndAlso (x.Longitude <= 51.605701446533196)) AndAlso ((x.Latitude >= 35.65952786487723) AndAlso (x.Latitude <= 35.75250288098893))))}` into `{((x.RealEstateFile.RealEstateStatus == Advertised) AndAlso (((x.Longitude >= 51.37413024902344) AndAlso (x.Longitude <= 51.605701446533196)) AndAlso ((x.Latitude >= 35.65952786487723) AndAlso (x.Latitude <= 35.75250288098893))))} ` – Beatles1692 Apr 27 '15 at 13:51
  • @Beatles1692 I've done a test and here it doesn't. Are you composing the expression in any way or do write it like `x => x.RealEstateStatus == value1 && x.Longitude >= value2 x.Longitude <= value3 && x.Latitude >= value4 && x.Latitude <= value5`? – xanatos Apr 27 '15 at 13:55
  • @Beatles1692 Because I'll say that you are composing the expression, perhaps by using some library, and the various "x" aren't the same "x" (the Expression library doesn't use "string" names for parameter matching). If you try to do `yourExpression.Compile()` before calling my method, does it work or does it throw an exception? – xanatos Apr 27 '15 at 13:56
  • I have never called `Compile` on this expression but `Linq To Nhibernate` provider translate it correctly. – Beatles1692 Apr 27 '15 at 14:56
  • @Beatles1692 There is a good possibility that Linq to NHibernate uses the names of the properties. QueryOver for NHibernate does. How do you build the query? Are you building it in a single expression, as in the example I gave you three messages ago or how? – xanatos Apr 27 '15 at 16:33
3

You can do it with a few way.

First and simplest: use Expression.Invoke

Expression<Func<T, bool>> exprT = t.Member1==someValue && t.Member2==someOtherValue
ParameterExpression p = Expression.Parameter(typeof(T1));
var expr = Expression.Invoke(expr, Expression.PropertyOrField(p, "Member"));
Expression<Func<T1, bool>> exprT1 = Expression.Lambda<Func<T1, bool>>(expr, p);

but in this case you get not

t1 => (t=>(t.Member1==someValue && t.Member2==someOtherValue))(t1.Member), 

instead of

(t1=>t1.Member.Member1==someValue && t1.Member.Member2==someOtherValue);

For replacing you can use ExpressionVisitor class like

    class V : ExpressionVisitor
    {
        public ParameterExpression Parameter { get; private set; }
        Expression m;
        public V(Type parameterType, string member)
        {
            Parameter = Expression.Parameter(parameterType);
            this.m = Expression.PropertyOrField(Parameter, member);
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (node.Type == m.Type)
            {
                return m;
            }
            return base.VisitParameter(node);
        }
    }

and use it

var v = new V(typeof(T1), "Member");
var exprT1 = Expression.Lambda<Func<T1, bool>>(v.Visit(exprT.Body), v.Parameter);
Grundy
  • 13,356
  • 3
  • 35
  • 55