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

自古美人都是妖i 提交于 2021-02-08 11:39:42

问题


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()?


回答1:


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);


来源:https://stackoverflow.com/questions/63876788/how-to-do-the-convertion-to-expression-tree-of-lambda-where-after-join-usi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!