问题
How to combine these expression
Expression<Func<int, int>> f1 = i => i + 1;
Expression<Func<int, int>> f2 = i => i + 2;
Expression<Func<int, int, int>> f3 = (i, j) => i * j;
to
Expression<Func<int, int>> f4 = i =>(i+1)*(i+2);
while running?
Here's the code. I want to write a extend method but it don't work in linq2entities
public static IQueryable<TRe> LeftJoin<TLeft, TRight, TKey, TRe>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TKey>> leftKeySel, Expression<Func<TRight, TKey>> rightKeySel, Expression<Func<TLeft, TRight, TRe>> reSel)
{
return left.GroupJoin(right, leftKeySel, rightKeySel, (l, r) => new { l, r }).SelectMany(p => p.r.DefaultIfEmpty(), (p, r) => new { p.l, r }).Select1(p => p.l, p => p.r, reSel);
}
public static IQueryable<TRe> Select1<TSrc, T1, T2, TRe>(this IQueryable<TSrc> src, Expression<Func<TSrc, T1>> f1, Expression<Func<TSrc, T2>> f2, Expression<Func<T1, T2, TRe>> func)
{
var p = Expression.Parameter(typeof(TSrc));
var a = Expression.Invoke(f1, p);
var b = Expression.Invoke(f2, p);
var c = Expression.Invoke(func, a, b);
return src.Select(Expression.Lambda<Func<TSrc, TRe>>(c, p));
}
and this code called LeftJoin method:
var re = _db.Accounts.OrderBy(p => p.LoginDays).Take(100).LeftJoin(_db.PasswordHistorys, p => p.Email, p => p.Email, (a, b) => new
{
a,
b.PasswordOld
});
回答1:
You can do it like this:
var p = Expression.Parameter(typeof(int), "i");
var r = Expression
.Invoke(f3, new[] {
Expression.Invoke(f1, p),
Expression.Invoke(f2, p) });
Expression<Func<int, int>> lam = Expression.Lambda<Func<int, int>>(r, p);
As per comments, it is possible to do this:
var p = Expression.Parameter(typeof(int), "i");
var lam = Expression.Lambda<Func<int, int>>(Expression.Multiply(f1.Body, f2.Body), p);
or
var p = Expression.Parameter(typeof(int), "i");
var lam = Expression.Lambda<Func<int, int>>(Expression.Add(f1.Body, f2.Body), p);
Solution with expression visitors
So, I managed to come up with a bit of a hack to replace the parameter references with f1 and f2.
However, it makes the following assumptions:
f3has exactly two parameters.f1,f2andf3all have the exact same method signatures
Here's the implementation:
public class SuperHack : ExpressionVisitor
{
private Dictionary<ParameterExpression, LambdaExpression> _replacements;
private ParameterExpression _newParameter;
public SuperHack(Dictionary<ParameterExpression, LambdaExpression> replacements, ParameterExpression newParameter)
{
_replacements = replacements ?? new Dictionary<ParameterExpression, LambdaExpression>();
_newParameter = newParameter;
}
public Expression Modify(Expression expression)
{
var res = Visit(expression);
return res;
}
protected override Expression VisitLambda<T>(Expression<T> e)
{
return Expression.Lambda(Visit(e.Body), _newParameter);
}
protected override Expression VisitParameter(ParameterExpression e)
{
if (_replacements.ContainsKey(e))
return Visit(Expression.Lambda(_replacements[e].Body, _newParameter).Body);
return base.VisitParameter(_newParameter);
}
}
And here's how you use it:
Expression<Func<int, int>> f1 = i => i + 1;
Expression<Func<int, int>> f2 = i => i + 2;
Expression<Func<int, int, int>> f3 = (i, j) => i * j;
var @params = f3.Parameters;
var mapping = new Dictionary<ParameterExpression, LambdaExpression>
{
{@params[0], f1},
{@params[1], f2}
};
var p = Expression.Parameter(typeof(int), "i");
var f4 = new SuperHack(mapping, p).Modify(f3) as Expression<Func<int,int>>;
The result is:
i => ((i + 1) * (i + 2))
No invokes needed!
回答2:
I wasn't entirely happy with my other solution (though it may be helpful to others who aren't concerned with Invoke calls within the Expression, so I'll leave it up).
The work-around to replace parameters with actual expressions is flaky at best, and very brittle. After a bit of thinking, it seems more logical to simply replace all Invoke calls with their respective expressions, substituting the parameter with the argument.
This allows you to write much more complex queries, and may be of use to someone who is migrating from Linq-To-Sql to EntityFramework. It also opens us up to performing further tweaks to allow EntityFramework to properly run.
End up writing something like this:
var p = Expression.Parameter(typeof(int), "i");
var r = Expression
.Invoke(f3, new[] {
Expression.Invoke(f1, p),
Expression.Invoke(f2, p) })
.InlineInvokes();
Expression<Func<int, int>> lam = Expression.Lambda<Func<int, int>>(r, p);
Which takes this expression:
i => Invoke((i, j) => (i * j), Invoke(i => (i + 1), i), Invoke(i => (i + 2), i))
And replaces it with this:
i => ((i + 1) * (i + 2))
Or for the more complex:
b => Invoke((d, e) => (d * e), Invoke(b => (50 + Invoke(z => (25 + Invoke(h => (h * 8), z)), b)), b), Invoke(c => (c + 2), b))
Produces
b => ((50 + (25 + (b * 8))) * (b + 2))
Implementation:
public static class ExpressionHelpers
{
public static Expression InlineInvokes<T>(this T expression)
where T : Expression
{
return (T)new InvokeInliner().Inline(expression);
}
public static Expression InlineInvokes(this InvocationExpression expression)
{
return new InvokeInliner().Inline(expression);
}
public class InvokeInliner : ExpressionVisitor
{
private Stack<Dictionary<ParameterExpression, Expression>> _context = new Stack<Dictionary<ParameterExpression, Expression>>();
public Expression Inline(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitInvocation(InvocationExpression e)
{
var callingLambda = ((LambdaExpression)e.Expression);
var currentMapping = new Dictionary<ParameterExpression, Expression>();
for (var i = 0; i < e.Arguments.Count; i++)
{
var argument = Visit(e.Arguments[i]);
var parameter = callingLambda.Parameters[i];
if (parameter != argument)
currentMapping.Add(parameter, argument);
}
_context.Push(currentMapping);
var result = Visit(callingLambda.Body);
_context.Pop();
return result;
}
protected override Expression VisitParameter(ParameterExpression e)
{
if (_context.Count > 0)
{
var currentMapping = _context.Peek();
if (currentMapping.ContainsKey(e))
return currentMapping[e];
}
return e;
}
}
}
How it works:
Whenever we hit an Invoke expression, we store the values being passed to the invoke, as well as the parameters of the expression being invoked. For example, if we had an invoke like this:
Invoke(f1, Expression.Constant(1)) and f1 was defined as i => { i + 1; } we define a mapping from i => Expression.Constant(1)
We then continue parsing the from the lambda expression's body, since we are no longer using parameters.
Then we capture visits to Parameter. Here, we look at the current mappings defined. If there's a mapping for the parameter, we return the substitute value verbatim. If there's no mapping, we simply return the parameter.
来源:https://stackoverflow.com/questions/34408179/linq-expression-calling-combines