Combina dos expresiones Linq lambda

Expression<Func> fn1 = x => x.PossibleSubPath.MyStringProperty; Expression<Func> fn2 = x => x.Contains("some literal"); 

¿Hay alguna forma de crear una nueva expresión lambda que básicamente use la salida de fn1 y la use como entrada para fn2?

 Expression<Func> fnCombined = ... 

Sé que puedo crear la función de una vez, pero el problema es que estoy creando un código genérico y, por lo tanto, realmente necesito poder crear estas dos funciones por separado, y luego combinarlas de tal manera que Linq pueda usarlas en mis objetos de base de datos (Entity Framework).

Entonces, lógicamente, lo que queremos hacer es crear una nueva lambda en la que tenga un parámetro de la entrada a la primera función, y un cuerpo que llame a la primera función con ese parámetro y luego pase el resultado como el parámetro al segunda función, y luego devuelve eso.

Podemos replicar eso fácilmente usando objetos Expression :

 public static Expression> Combine( Expression> first, Expression> second) { var param = Expression.Parameter(typeof(T1), "param"); var body = Expression.Invoke(second, Expression.Invoke(first, param)); return Expression.Lambda>(body, param); } 

Lamentablemente, EF y la mayoría de los otros proveedores de consultas realmente no sabrán qué hacer con eso y no funcionarán correctamente. Cada vez que tocan una expresión Invoke , generalmente lanzan una excepción de algún tipo. Algunos pueden manejarlo sin embargo. En teoría, toda la información que necesitan está allí, si están escritos con la solidez necesaria para lograrlo.

Sin embargo, lo que podemos hacer es, desde un punto de vista conceptual, reemplazar cada instancia del primer parámetro lambda en el cuerpo de lambda con el parámetro de una nueva lambda que estamos creando, y luego reemplazar todas las instancias del segundo parámetro lambda en la segunda lambda con el nuevo cuerpo de la primera lambda. Técnicamente, si estas expresiones tienen efectos secundarios, y estos parámetros se usan más de una vez, no serían los mismos, pero como estos serán analizados por un proveedor de consultas EF, realmente no deberían tener efectos secundarios.

Gracias a David B por proporcionar un enlace a esta pregunta relacionada que proporciona una implementación ReplaceVisitor . Podemos usar ese ReplaceVisitor para recorrer todo el árbol de una expresión y reemplazar una expresión por otra. La implementación de ese tipo es:

 class ReplaceVisitor : ExpressionVisitor { private readonly Expression from, to; public ReplaceVisitor(Expression from, Expression to) { this.from = from; this.to = to; } public override Expression Visit(Expression node) { return node == from ? to : base.Visit(node); } } 

Y ahora podemos escribir nuestro método Combine adecuado :

 public static Expression> Combine( this Expression> first, Expression> second) { var param = Expression.Parameter(typeof(T1), "param"); var newFirst = new ReplaceVisitor(first.Parameters.First(), param) .Visit(first.Body); var newSecond = new ReplaceVisitor(second.Parameters.First(), newFirst) .Visit(second.Body); return Expression.Lambda>(newSecond, param); } 

y un caso de prueba simple, para demostrar lo que está sucediendo:

 Expression> fn1 = x => x.PossibleSubPath.MyStringProperty; Expression> fn2 = x => x.Contains("some literal"); var composite = fn1.Combine(fn2); Console.WriteLine(composite); 

Que se imprimirá:

param => param.PossibleSubPath.MyStringProperty.Contains (“some literal”)

Que es exactamente lo que queremos; un proveedor de consultas sabrá cómo analizar algo así.