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
});
你可以
这样做:
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);
<小时 />使用表达式访问者的解决方案
因此,我设法想出了一些技巧,将参数引用替换为f1
和f2
。
但是,它做出以下假设:
-
f3
正好有两个参数。 -
f1
、f2
和f3
都有完全相同的方法签名
下面是实现:
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
的访问。在这里,我们查看定义的当前映射。如果参数有映射,我们将逐字返回替换值。如果没有映射,我们只需返回参数。