Linq表达式调用组合

时间:2015-12-22 03:54:56

标签: c# linq linq-to-sql 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;

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

跑步时?

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

2 个答案:

答案 0 :(得分:3)

你可以这样做:

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都具有完全相同的方法签名
  3. 以下是实施:

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

    不需要调用!

答案 1 :(得分:0)

我对我的其他解决方案并不完全满意(虽然它可能对那些不关心表达式中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的访问。在这里,我们看一下定义的当前映射。如果有参数的映射,我们将逐字地返回替换值。如果没有映射,我们只需返回参数。