问题
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