Search code examples
c#linqlambdaexpressionexpression-trees

How to do the convertion to Expression tree of lambda .Where() after .join() using anonymous type?


How to finish the convertion of the following query to Expression tree syntax, using an anonymous type in the .Where() and .Select()?

IQueryable<A> As = db.A
                     .Join(
                           db.B,
                           _a => _a.bID,
                           _b => _b.ID,
                           (a, b) => new { a, b })

                     //next two are the objective
                     .Where(s=> s.b.Name == "xpto")
                     .Select(s => s.a);

At this point I have (Thanks to @NetMage):

//QUERY DB with GENERIC TYPE
IQueryable<B> queryable = (IQueryable<B>)db.B;


// IQueryable<TOuter>
var arg0 = Expression.Constant(db.A.AsQueryable());

// IEnumerable<TInner>
var arg1 = Expression.Constant(queryable);

// TOuter 
var arg2p = Expression.Parameter(modelType2, "_a");
// TKey
var arg2body = Expression.PropertyOrField(arg2p, "_bID");
// also TKey 
var arg2 = Expression.Lambda(arg2body, arg2p);


// TInner
var arg3p = Expression.Parameter(typeof(B), "_b");
// TKey
var arg3body = Expression.PropertyOrField(arg3p, "ID");

var arg3 = Expression.Lambda(arg3body, arg3p);

// TResult 
var anonymousType = (new { a = db.A.FirstOrDefault(), b = db.B.FirstOrDefault() }).GetType();
// .ctor
var arg4Constructor = anonymousType.GetConstructors()[0];
// 
var arg4A = arg2p;
// pet.Name
var arg4B =arg3p;
// Type array
var arg4Args = new[] { arg4A, arg4B };

var arg4Members = anonymousType.GetProperties();

var arg4body = Expression.New(arg4Constructor, arg4Args, arg4Members);
// 
var arg4 = Expression.Lambda(arg4body, arg2p, arg3p);

MethodInfo joinGenericMI = typeof(Queryable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name == "Join" && m.GetParameters().Length == 5)
.First();


var joinMI = joinGenericMI.MakeGenericMethod(new[] { arg2p.Type, arg3p.Type, arg2.ReturnType, anonymousType });
var qExpr = Expression.Call(joinMI, arg0, arg1, arg2, arg3, arg4);

For the .Where() I tryed:

//.Where(s => s.b.Name == "xpto")
    //s
    ParameterExpression s = Expression.Parameter(anonymousType, "s");
    //s.b
    Expression left1 = Expression.Property(s, anonymousType.GetProperty("b"));
    //s.b.Name
    Expression left2 = Expression.Property(left1, "Name");
    //"xpto"
    Expression right = Expression.Constant("xpto", typeof(string));
    //s.b.Name="xpto"
    Expression e2 = Expression.Equal(left2, right);

    ParameterExpression t = Expression.Parameter(typeof(string), "t");
    //BLOCK WHERE
    MethodCallExpression whereCallExpression = Expression.Call(
         typeof(Queryable),
         "Where",
         new Type[] { typeof(A) },
         qExpr, // Queryable with join
         Expression.Lambda<Func<string, bool>>(e2, new ParameterExpression[] { t })); //'e2' is the where Expression, and 't' the input string for the comparison 
    //BLOCK WHERE

which raises something like:

InvalidOperation: No generic method "where" of type 'System.Linq.Queryable' is compatible with the arguments, and the type arguments. You should not give type arguments if it isn't a generic method (this is a rough translation of the error).

And I bet that it will also be some trickery in the select...

How to do the convertion to Expression tree of lambda .Where() using anonymous type after the .join()?


Solution

  • Using this minified version of my ExpressionExt class for adding extensions to make Expression tree building somewhat easier:

    public static class ValueTupleExt {
        private static T[] makeArray<T>(params T[] itemArray) => itemArray;
        public static T[] ToArray<T>(this (T, T) tuple) => makeArray(tuple.Item1, tuple.Item2);    
    }
    
    public static class ExpressionExt {
        private static Type TQueryable = typeof(Queryable);
    
        private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
    
        public static MethodCallExpression Join(this Expression outer, Expression inner, LambdaExpression outerKeyFne, LambdaExpression innerKeyFne, LambdaExpression resultFne) =>
                Expression.Call(TQueryable, "Join", new[] { outer.TypeGenArg(0), inner.TypeGenArg(0), outerKeyFne.ReturnType, resultFne.ReturnType }, outer, inner, outerKeyFne, innerKeyFne, resultFne);
    
        public static MethodCallExpression Select(this Expression src, LambdaExpression resultFne) => Expression.Call(TQueryable, "Select", new[] { src.TypeGenArg(0), resultFne.ReturnType }, src, resultFne);
    
        public static MethodCallExpression Where(this Expression src, LambdaExpression predFne) => Expression.Call(TQueryable, "Where", new[] { src.TypeGenArg(0) }, src, predFne);
    
        public static ConstantExpression AsConst<T>(this T obj) => Expression.Constant(obj, typeof(T));
    
        public static MemberExpression Dot(this Expression obj, string propNames) =>
            (MemberExpression)propNames.Split('.').Aggregate(obj, (ans, propName) => Expression.PropertyOrField(ans, propName));
    
        public static LambdaExpression Lambda(this ParameterExpression p1, Expression body) => Expression.Lambda(body, p1);
        public static LambdaExpression Lambda(this (ParameterExpression, ParameterExpression) parms, Expression body) => Expression.Lambda(body, parms.ToArray());
    
        public static NewExpression New(this Type t, params Expression[] vals) => Expression.New(t.GetConstructors()[0], vals, t.GetProperties());
    
        public static BinaryExpression opEq(this Expression left, Expression right) => Expression.Equal(left, right);
    
        public static ParameterExpression Param(this Type t, string pName) => Expression.Parameter(t, pName);
    }
    

    You can take the query:

    IQueryable<A> As = db.A
                         .Join(
                               db.B,
                               _a => _a.bID,
                               _b => _b.ID,
                               (_a, _b) => new { a = _a, b = _b })
                         .Where(s => s.b.Name == "xpto")
                         .Select(s => s.a);
    

    And recreate it using an Expression tree with:

    var aParm = typeof(A).Param("_a");
    var aKeyFne = aParm.Lambda(aParm.Dot("bID"));
    
    var bParm = typeof(B).Param("_b");
    var bKeyFne = bParm.Lambda(bParm.Dot("ID"));
    var anonType = (new { a = default(A), b = default(B) }).GetType();
    var resultFne = (aParm, bParm).Lambda(anonType.New(aParm, bParm));
    var join = db.A.AsConst().Join(db.B.AsConst(), aKeyFne, bKeyFne, resultFne);
    
    var sParm = anonType.Param("s");
    var predFne = sParm.Lambda(sParm.Dot("b.Name").opEq("xpto".AsConst()));
    
    var where = join.Where(predFne);
    var qexpr = where.Select(sParm.Lambda(sParm.Dot("a")));
    
    IQueryable<A> AsE = new System.Linq.EnumerableQuery<A>(qexpr);