Linq Expression Calling Combines

拈花ヽ惹草 提交于 2019-12-12 06:49:50

问题


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:

  1. f3 has exactly two parameters.
  2. f1 ,f2 and f3 all 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

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