如何在使用匿名类型的.join之后转换为lambda .Where的表达式树?

问题描述

如何在.Where()和.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);

至此,我有了(感谢@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);

对于我尝试过的.Where():

//.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

它引发类似以下内容的

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

我敢打赌,这在选择中也会有些诡异...

如何在.join()之后使用匿名类型转换为lambda .Where()到表达式树?

解决方法

使用我的ExpressionExt class的缩小版来添加扩展名,以使Expression树的构建更加容易:

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),src,resultFne);

    public static MethodCallExpression Where(this Expression src,LambdaExpression predFne) => Expression.Call(TQueryable,"Where",new[] { src.TypeGenArg(0) },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,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);
}

您可以进行查询:

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

并使用带有以下内容的Expression树重新创建它:

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...