1

I am dynamically generating Expression trees to be sent off to LINQ to Entities. I am providing a framework and I allow developers to specify output columns as lambda expressions.

For instance, say they have a column they can specify that the way to pull the value from the database is:

p => p.Aliases.Count()

and another column is:

p => p.Names.Where(n => n.StartsWith("hello"))

The problem here is that I need to combine both of these into a single expression.

My first attempt was:

getter = field.SelectorExpression.Body;

And I get the error message The parameter 'p' was not bound in the specified LINQ to Entities query expression which makes since because LINQ can't know what P is in p.Aliases.Count().

The next thing I tried was:

getter = Expression.Invoke(field.OrderBySelector, new[] {parameter});

However, then I recieve the message The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. Which also makes since because I don't expect SQL Server to know how to run lambda expressions.

Right now basically have this expression:

item => new MyClass
{ 
    SomeValue = item.Name, // this was generated from some other code
    AnotherValue = item.SomeOtherColumn, // there can be lots of these
    AliasCount = p.Aliases.Count() // here of course is the problem
}

Obviously I want to replace the expression with the "p" when I am building this with the expression for item (which I have and know how to use).

TLDR Is there an easy way to replace all instances of a parameter usaged in a LambdaExpression with another Expression?

tster
  • 17,883
  • 5
  • 53
  • 72
  • 1
    Like http://stackoverflow.com/questions/5430996/replacing-the-parameter-name-in-the-body-of-an-expression/5431309#5431309 maybe? Let me know if that answers it (the "visitor" approach is the first to try) – Marc Gravell Apr 29 '11 at 19:31
  • Would you care to explain it a bit better? I have read all but did not understand :/ p => p.Names.Where(n => n.StartsWith("hello")) - is a single expression where one of the parameters in the parameter to the single expression is another expression... – Marino Šimić Apr 29 '11 at 19:34
  • Marc Gravell, you are literally I god among men. you answered this faster than I could put it in my code and test it! – tster Apr 29 '11 at 19:40
  • Note that I am voting to close as this is an exact duplicate – tster Apr 29 '11 at 19:41

1 Answers1

0

An Example to get you started.

class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<Foo1, int>> expression1 = a => a.Foo2S.Count();

            Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression2 =
                a => a.Select(b => new Foo2 { String1 = "asdf", Foo2Count = 3 });

            MemberAssignment foo2countAssignment = GetExpression(expression2);
            // in this case, it will be a ConstantExpression with a value of 3.
            var expression = foo2countAssignment.Expression as ConstantExpression;

            Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression3 =
                a => a.Select(b => new Foo2 { String1 = "asdf", Foo2Count = b.Foo2S.Count() });

            foo2countAssignment = GetExpression(expression3);
            // in this case, it will be an Expression<Func<Foo1, int>>
            // exactly the same as expression1, except that it has a different parameter.
            var expressionResult = foo2countAssignment.Expression as MethodCallExpression;
            var foo2SPropertyExpression = expressionResult.Arguments[0] as MemberExpression;
            // This is the "b".Foo2SCount()
            var theBparameter = foo2SPropertyExpression.Expression as ParameterExpression;


            // Practical demonstartion.
            var mce = expression1.Body as MethodCallExpression;

            var selectStatement = expression2.Body as MethodCallExpression;
            var selectLambda = selectStatement.Arguments[1] as Expression<Func<Foo1, Foo2>>;
            var bParameter = selectLambda.Parameters[0];

            var me = mce.Arguments[0] as MemberExpression;
            var newExpression = me.Update(bParameter);
            // Then you go up the expression tree using Update to create new expression till first level.
            // Unless you find a way to replace me.
        }

        public static MemberAssignment GetExpression(Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression2)
        {
            // a."Select"
            var selectStatement = expression2.Body as MethodCallExpression;
            // a.Select("b => new Foo2..."
            var selectLambda = selectStatement.Arguments[1] as Expression<Func<Foo1, Foo2>>;
            // a.Select(b => "new Foo2"
            var newFoo2Statement = selectLambda.Body as MemberInitExpression;
            // a.Select(b => new Foo2 {string1 = "asdf", !!Foo2Count = 3!! })
            return newFoo2Statement.Bindings[1] as MemberAssignment;
        }
    }

        public class Foo1
    {
        public IEnumerable<Foo2> Foo2S { get; set; }
    }

    public class Foo2
    {
        public string String1 { get; set; }
        public int Foo2Count { get; set; }
    }

Basically this program traverses the expression tree and lays out each node. You can use ".GetType()" on each node to get the exact type of expression and deal them accordingly. (Hardcoded and known before hand in this example).

The last example demonstrates how you can replace the " a " in the a.Foo2s.Count() to " b " so that it can be replaced into the second longer expression.

Then ofcourse you need to think of a way so that you can auto detect and replicated all of expression 1 backinto expression 2.

Not an easy task.

Sleeper Smith
  • 3,212
  • 4
  • 28
  • 39
  • Note that this SO question http://stackoverflow.com/questions/5430996/replacing-the-parameter-name-in-the-body-of-an-expression/5431309#5431309 answered my question directly. – tster May 12 '11 at 13:08