2016-08-03 2 views
1

Допустим у меня есть Expression<Func<Foo, Bar>>calculateBar, который «уменьшает» в Bar в Foo, который я могу использовать так:Есть ли способ более легко сочетать выражения и лямбды?

IQueryable foo = getFoos(); 
bars = foo.Select(calculateBar); 

Но иногда мне нужно, чтобы иметь возможность ссылаться на вход Foo, так что я хочу обернуть calculateBar так что он может возвращать 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 конструкторы должны быть без параметров. Поэтому вместо этого я могу создать поддельный Tuple (new WithInput<Foo, Bar> { Input = theFoo, Output = theBar }), но писать это как выражение будет довольно болезненным.

Есть ли способ построить существующее выражение (без нарушения LINQ-to-Entities), используя Lambda, вместо того чтобы продолжать строить больше Expression деревьев?

Например (psuedocode):

Expression<Func<Foo, WithInput<Foo, Bar>>> wrapper = foo => new WithInput { Input = foo, Output = Expression.Invoke(calculateBar, foo) }; 

ответ

1

Дать выражение 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); 
} 

Сейчас на главной теме. Невозможно построить выражение на основе существующей лямбда, не используя какую-либо специальную библиотеку утилиты обработки выражений (либо вашу собственную, либо третью сторону).

Например, 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(); 
} 
+0

Brilliant, спасибо, 'MemberInit' велик. –

Смежные вопросы