Ho Expression<Func<Entity, string>>
che può essere una proprietà o una proprietà accessoria nidificata
y => y.SearchColumn
o
y => y.SubItem.SubColumn
Sto costruendo un albero di espressioni in modo dinamico e vorrei ottenere un InvokeExpression come questo
x => x.SearchColumn.Contains("foo");
x => x.Sub.SearchColumn.Contains("foo");
Sono abbastanza sicuro di poter scartare il corpo dell'espressione, quindi applicare parzialmente x ParameterExpression
ma non riesco a capire gli incantesimi magici per farlo accadere
Quindi ho qualcosa di simile
Expression<Func<Entity, string>> createContains(Expression<Func<Entity, string>> accessor) {
var stringContains = typeof(String).GetMethod("Contains", new [] { typeof(String) });
var pe = Expression.Parameter(typeof(T), "__x4326");
return Expression.Lambda<Func<Entity, bool>>(
Expression.Call(
curryExpression(accessor.Body, pe),
stringContains,
Expression.Constant("foo")
)
, pe
);
}
static Expression curryExpression(Expression from, ParameterExpression parameter) {
// this doesn't handle the sub-property scenario
return Expression.Property(parameter, ((MemberExpression) from).Member.Name);
//I thought this would work but it does not
//return Expression.Lambda<Func<Entity,string>>(from, parameter).Body;
}
Modifica: ecco l'intera cosa che sto cercando di fare insieme all'errore
Devi fare due cose: prima puoi usare lo stesso accessor.Body
, ma farà riferimento a Parametro errato, quando ne hai creato uno nuovo. Secondo quello che si ha bisogno di scrivere personalizzato ExpressionVisitor
che andrà a sostituire tutto l'uso di originali y
ParameterExpression
ad una nuova creazione, in modo da provocare l'espressione sarà compilato bene.
Se non hai bisogno di creare un nuovo ParameterExpression
, puoi semplicemente utilizzare lo stesso accessor.Body
e inviare nuovamente ParameterExpression
originale a un nuovo albero.
Quindi ecco la mia copia di lavoro di prova con una nuova ParameterExpression
- https://dotnetfiddle.net/uuPVAl
E il codice stesso
public class Program
{
public static void Main(string[] args)
{
Expression<Func<Entity, string>> parent = (y) => y.SearchColumn;
Expression<Func<Entity, string>> sub = (y) => y.Sub.SearchColumn;
var result = Wrap(parent);
Console.WriteLine(result);
result.Compile();
result = Wrap(sub);
Console.WriteLine(result);
result.Compile();
result = Wrap<Entity>((y) => y.Sub.Sub.Sub.SearchColumn);
Console.WriteLine(result);
result.Compile();
}
private static Expression<Func<T, bool>> Wrap<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof (String).GetMethod("Contains", new[] {typeof (String)});
var pe = Expression.Parameter(typeof (T), "__x4326");
var newBody = new ParameterReplacer(pe).Visit(accessor.Body);
var call = Expression.Call(
newBody,
stringContains,
Expression.Constant("foo")
);
return Expression.Lambda<Func<T, bool>>(call, pe);
}
}
public class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression _target;
public ParameterReplacer(ParameterExpression target)
{
_target = target;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// here we are replacing original to a new one
return _target;
}
}
public class Entity
{
public string SearchColumn { get; set; }
public Entity Sub { get; set; }
}
PS: questo esempio funzionerà solo se hai una sola ParameterExpression
nella query originale, altrimenti il visitatore dovrebbe differenziarle
AGGIORNARE
Ecco la mia risposta di lavoro con il tuo esempio completo in aggiornamento - https://dotnetfiddle.net/MXP7wE
Hai solo bisogno di aggiustare un paio di cose:
Expression<Func<T, bool>>
. Expression.Call()
dovrebbe essere semplicemente accessor.Body
. ParameterExpression
parametro al Expression.Lambda<Func<T, bool>>()
chiamata al metodo deve semplicemente essere impostato dal accessor
parametro s'. Metodo:
Expression<Func<T, bool>> CreateContains<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof(String).GetMethod("Contains", new[] { typeof(String) });
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
accessor.Body,
stringContains,
Expression.Constant("foo")
)
, accessor.Parameters[0]
);
}