有没有一种方法可以更轻松地组合表达式和 lambda?
Is there a way to more easily combine Expressions and lambdas?
假设我有一个 Expression<Func<Foo, Bar>>
calculateBar
,其中 'reduces' 一个 Bar
到一个 Foo
,我可以这样使用它:
IQueryable foo = getFoos();
bars = foo.Select(calculateBar);
但是,有时我需要能够引用输入 Foo,所以我想包装 calculateBar
这样它就可以 return a Tuple<Foo, Bar>
:
public static Expression<Func<TIn, Tuple<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
var param = Expression.Parameter(typeof(TIn));
var constructor = typeof(Tuple<TIn, TOut>).GetConstructor(new[] { typeof(TIn), typeof(TOut) });
if (constructor == null) throw new ArgumentNullException();
return Expression.Lambda<Func<TIn, Tuple<TIn, TOut>>>(Expression.New(constructor, param, Expression.Invoke(expression, param)), param);
}
现在,该功能在实践中运行良好。但是,在 LINQ-to-Entities 中,构造函数必须是无参数的。因此,相反,我可能想创建一个假元组 (new WithInput<Foo, Bar> { Input = theFoo, Output = theBar }
),但是将其写为表达式会相当痛苦。
有没有一种方法可以使用 Lambda 在现有表达式的基础上构建(不扰乱 LINQ-to-Entities)而不是继续构建更多 Expression
树?
例如(伪代码):
Expression<Func<Foo, WithInput<Foo, Bar>>> wrapper = foo => new WithInput { Input = foo, Output = Expression.Invoke(calculateBar, foo) };
写一个 MemberInit
表达式比写一个 Tuple
没有那么痛苦。只是为了记录,它会是这样的:
public static Expression<Func<TIn, WithInput<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
var parameter = expression.Parameters[0];
var resultType = typeof(WithInput<TIn, TOut>);
var body = Expression.MemberInit(Expression.New(resultType),
Expression.Bind(resultType.GetProperty("Input"), parameter),
Expression.Bind(resultType.GetProperty("Output"), expression.Body));
return Expression.Lambda<Func<TIn, WithInput<TIn, TOut>>>(body, parameter);
}
现在进入正题。如果不使用某些自定义表达式处理实用程序库(您自己的或第 3 方),则无法基于现有的 lambda 构建表达式。
例如,LINQKit 提供了 Invoke
和 Expand
扩展方法,可以像这样使用:
using LinqKit;
public static Expression<Func<TIn, WithInput<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
return Linq.Expr((TIn input) => new WithInput<TIn, TOut>
{
Input = input,
Output = expression.Invoke(input)
}).Expand();
}
假设我有一个 Expression<Func<Foo, Bar>>
calculateBar
,其中 'reduces' 一个 Bar
到一个 Foo
,我可以这样使用它:
IQueryable foo = getFoos();
bars = foo.Select(calculateBar);
但是,有时我需要能够引用输入 Foo,所以我想包装 calculateBar
这样它就可以 return a Tuple<Foo, Bar>
:
public static Expression<Func<TIn, Tuple<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
var param = Expression.Parameter(typeof(TIn));
var constructor = typeof(Tuple<TIn, TOut>).GetConstructor(new[] { typeof(TIn), typeof(TOut) });
if (constructor == null) throw new ArgumentNullException();
return Expression.Lambda<Func<TIn, Tuple<TIn, TOut>>>(Expression.New(constructor, param, Expression.Invoke(expression, param)), param);
}
现在,该功能在实践中运行良好。但是,在 LINQ-to-Entities 中,构造函数必须是无参数的。因此,相反,我可能想创建一个假元组 (new WithInput<Foo, Bar> { Input = theFoo, Output = theBar }
),但是将其写为表达式会相当痛苦。
有没有一种方法可以使用 Lambda 在现有表达式的基础上构建(不扰乱 LINQ-to-Entities)而不是继续构建更多 Expression
树?
例如(伪代码):
Expression<Func<Foo, WithInput<Foo, Bar>>> wrapper = foo => new WithInput { Input = foo, Output = Expression.Invoke(calculateBar, foo) };
写一个 MemberInit
表达式比写一个 Tuple
没有那么痛苦。只是为了记录,它会是这样的:
public static Expression<Func<TIn, WithInput<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
var parameter = expression.Parameters[0];
var resultType = typeof(WithInput<TIn, TOut>);
var body = Expression.MemberInit(Expression.New(resultType),
Expression.Bind(resultType.GetProperty("Input"), parameter),
Expression.Bind(resultType.GetProperty("Output"), expression.Body));
return Expression.Lambda<Func<TIn, WithInput<TIn, TOut>>>(body, parameter);
}
现在进入正题。如果不使用某些自定义表达式处理实用程序库(您自己的或第 3 方),则无法基于现有的 lambda 构建表达式。
例如,LINQKit 提供了 Invoke
和 Expand
扩展方法,可以像这样使用:
using LinqKit;
public static Expression<Func<TIn, WithInput<TIn, TOut>>> WithInput<TIn, TOut>(
this Expression<Func<TIn, TOut>> expression)
{
return Linq.Expr((TIn input) => new WithInput<TIn, TOut>
{
Input = input,
Output = expression.Invoke(input)
}).Expand();
}