Quindi logicamente ciò che vogliamo essere in grado di fare è creare una nuova lambda in cui ha un parametro di input per la prima funzione, e un corpo che chiama la prima funzione con quel parametro e quindi passa il risultato come il parametro per la seconda funzione, quindi restituisce quello.
Possiamo replicare che abbastanza facilmente usando Expression
oggetti:
public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> second)
{
var param = Expression.Parameter(typeof(T1), "param");
var body = Expression.Invoke(second, Expression.Invoke(first, param));
return Expression.Lambda<Func<T1, T3>>(body, param);
}
Purtroppo, EF e la maggior parte di altri fornitori di query in realtà non so cosa fare con quello e non funzionerà correttamente. Ogni volta che colpiscono un'espressione Invoke
, in genere generano un'eccezione di qualche tipo. Alcuni possono gestire lo stesso. In teoria tutte le informazioni di cui hanno bisogno sono lì, se sono scritte con la robustezza per arrivarci.
Ciò che possiamo fare è, dal punto di vista concettuale, sostituire ogni istanza del primo parametro lambda nel corpo di quella lambda con il parametro di un nuovo lambda che stiamo creando e quindi sostituire tutte le istanze del secondo parametro lambda nella seconda lambda con il nuovo corpo della prima lambda. Tecnicamente, se queste espressioni hanno effetti collaterali, e questi parametri sono usati più di una volta, non sarebbero gli stessi, ma poiché questi verranno analizzati da un provider di query EF, non dovrebbero mai avere effetti collaterali.
Grazie a David B per aver fornito un collegamento a this related question che fornisce un'implementazione ReplaceVisitor
. Possiamo usare quello ReplaceVisitor
per passare attraverso l'intero albero di un'espressione e sostituire un'espressione con un'altra. L'implementazione di questo tipo è:
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);
}
}
Ed ora possiamo scrivere il nostro correttaCombine
metodo:
public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> 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<Func<T1, T3>>(newSecond, param);
}
e un semplice banco di prova, per dimostrare proprio quello che sta succedendo:
Expression<Func<MyObject, string>> fn1 = x => x.PossibleSubPath.MyStringProperty;
Expression<Func<string, bool>> fn2 = x => x.Contains("some literal");
var composite = Combine(fn1, fn2);
Console.WriteLine(composite);
Quale verrà stampato:
param => param.PossibleSubPath.MyStringProperty.Contains ("un po 'letterale")
che è esattamente quello che vogliamo; un provider di query saprà come analizzare qualcosa del genere.
Ecco una risposta che mostra la manipolazione/combinazione di espressioni. http://stackoverflow.com/a/9683506/8155 –
@DavidB Grazie per il collegamento; Stavo avendo difficoltà a capire come sostituire tutte le istanze di un'espressione con un'altra. – Servy
Grazie mille, ragazzi! –