Search code examples
c#entity-frameworkexpression-trees

Calling the .Any Extension Method with Entity Framework using Expression Trees


I have looked at few examples here Calling a Method from an Expression and on MSDN but I have not been able to get the right method call/object type for Any() for the query below. I seem to be able to get the property call but not IEnumerable part of the child property.
billing_map_set_lu is the parent of billmaps_lu and is defined as an association in the Entity Framework.

The reason I am using expression trees is that I need to be able to define the query at runtime with 1-n .SelectMany(p => p.billmaps_lu).Where(predicate) clauses. So I figured if I could build the expression trees I could handle all the different combinations I have for this system which are many.

var myResults = ctx.billing_map_set_lu
                   .Where(p => p.billmaps_lu.Any(b => b.billmap_columnname == "templatesittings_key" &&  b.billmap_columnvalue == 428264))
                                   SelectMany(p => p.billmaps_lu)
                   .Where (b =>b.billmap_columnname =="locations_key" && b.billmap_columnvalue == 12445)
                                   Select(z => z.billing_map_set_lu);

I have tried a quite a few attempts using the samples above...

ParameterExpression bms = Expression.Parameter(typeof(billmaps_lu));
Expression left1 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnname"));
Expression right1 = Expression.Constant("templatesittings_key", typeof(string));
Expression InsideAny1 = Expression.Equal(left1, right1);
Expression left2 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnvalue"));
Expression right2 = Expression.Constant(428264, typeof(int));
Expression InsideAny2 = Expression.Equal(left2, right2);
Expression myWhereClause1 = Expression.AndAlso(InsideAny1, InsideAny2);

The above part seems fine but when I try to do the .Any It is like I can't get the right property/method to get the right objects out. (I feel like I am on a physics problem where I am working with the wrong units.) I am hoping it is something simple that I am missing, I am pretty new to Expression Trees.. I have included non-working code to try to show you where my head is at and how someone can steer me in the right direction.

MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(billing_map_set_lu).GetProperty("billmaps_lu").PropertyType);
ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
ParameterExpression billMaps = Expression.Parameter(typeof(billmaps_lu), "p1");
var myFunction = Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(billMapSetParameter, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)

Solution

  • Disclaimer, I haven't got any compiled working code.

    2 problems.

    First problem probably lies in:

    ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
    

    That's not a parameter you need in:

    Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(**billMapSetParameter**, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)
    

    Change the billMapSetParameter to the billMaps ParamterExpression, then you should be good to go. You are calling the PropertyExpression to obtain your billMapSet for you from the ParameterExpression.

    2nd problem: (Not sure, but my gut feeling) You may need to pass the Where clause as a ConstantExpression with type Expression<.Func<>>. .Any method takes two parameters, of which, the second is an Expression<.Func<>> (Or just a Func<>? can't remember).

    var whereExpression = Expression.Lambda<.Func<.billmaps_lu, bool>>(myWhereClause1, bms);
    var ce = Expression.Constant(whereExpression)
    

    Then pass back ce into originally where you "myWhereClause1" is.

    Cross finger it works

    Edit- Scrap that, SHOW MI ZEH CODEZ

    public class Foo
    {
        public List<string> Strings { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Func<Foo, bool> func =
                a => a.Strings.Any(b => b == "asdf");
    
            // b => b == "asdf";
            var bParameter = Expression.Parameter(typeof (string));
            var asdfConstant = Expression.Constant("asdf");
            var compare = Expression.Equal(bParameter, asdfConstant);
            var compareExpression = Expression.Lambda<Func<string, bool>>(compare, bParameter);
            var ceCompareExpression = Expression.Constant(compareExpression.Compile());
    
            // a => a.Strings.Any(compareExpression)
            var parameter = Expression.Parameter(typeof (Foo));
    
            var foosProperty = Expression.Property(parameter, typeof (Foo).GetProperty("Strings"));
            MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
    
            var anyMethod = Expression.Call(method, foosProperty, ceCompareExpression);
    
            var lambdaExpression = Expression.Lambda<Func<Foo, bool>>(anyMethod, parameter);
    
            // Test.
            var foo = new Foo {Strings = new List<string> {"asdf", "fdsas"}};
    
            Console.WriteLine(string.Format("original func result: {0}", func(foo)));
            Console.Write(string.Format("constructed func result: {0}", lambdaExpression.Compile()(foo)));
    
            Console.ReadKey();
        }
    }