Linq 表达式调用组合

本文关键字:组合 调用 表达式 Linq | 更新日期: 2023-09-27 18:27:38

如何组合这些表达式

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

Linq 表达式调用组合

你可以

这样做:

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. f1f2f3都有完全相同的方法签名

下面是实现:

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 表达式主体中的 ,因为我们不再使用参数。

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