Linq 表达式调用组合

Linq Expression Calling Combines

如何组合这些表达式

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;

Expression<Func<int, int>> f4 = i =>(i+1)*(i+2);

而 运行?


这是代码。我想写一个扩展方法,但它在 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));
}

此代码调用了 LeftJoin 方法:

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

你可以这样做:

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

根据评论,可以这样做:

var p = Expression.Parameter(typeof(int), "i");
var lam = Expression.Lambda<Func<int, int>>(Expression.Multiply(f1.Body, f2.Body), p);

var p = Expression.Parameter(typeof(int), "i");
var lam = Expression.Lambda<Func<int, int>>(Expression.Add(f1.Body, f2.Body), p);

带有表达式访问者的解决方案

所以,我设法想出了一些技巧,用 f1f2 替换参数引用。
但是,它做出以下假设:

  1. f3 正好有两个参数。
  2. f1 ,f2f3 都有完全相同的方法签名

实现如下:

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

下面是你如何使用它:

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

结果是:

i => ((i + 1) * (i + 2))

无需调用!

我对我的其他解决方案并不完全满意(尽管它可能对其他不关心表达式中的 Invoke 调用的人有帮助,所以我将保留它)。

用实际表达式替换参数的变通方法充其量是片状的,而且非常脆弱。经过一番思考,将所有 Invoke 调用简单地替换为它们各自的表达式,将参数替换为参数似乎更合乎逻辑。

这允许您编写更复杂的查询,并且可能对从 Linq-To-Sql 迁移到 EntityFramework 的人有用。它还使我们能够进行进一步的调整,以允许 EntityFramework 正确地 运行。

最后写成这样:

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

采用这个表达式:

i => Invoke((i, j) => (i * j), Invoke(i => (i + 1), i), Invoke(i => (i + 2), i))

并将其替换为:

i => ((i + 1) * (i + 2))

或者更复杂的:

b => Invoke((d, e) => (d * e), Invoke(b => (50 + Invoke(z => (25 + Invoke(h => (h * 8), z)), b)), b), Invoke(c => (c + 2), b))

生产

b => ((50 + (25 + (b * 8))) * (b + 2))

实施:

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

工作原理:

每当我们点击 Invoke 表达式时,我们都会存储传递给调用的值,以及被调用的表达式的参数。例如,如果我们有这样的调用:

Invoke(f1, Expression.Constant(1))f1 被定义为 i => { i + 1; } 我们从 i => Expression.Constant(1)

定义一个映射

然后我们继续从 lambda 表达式的 body 中解析,因为我们不再使用参数。

然后我们捕获对 Parameter 的访问。在这里,我们查看当前定义的映射。如果参数有映射,我们 return 逐字替换值。如果没有映射,我们只需 return 参数。