2015-08-31 37 views
6

Ho un Expression<Func<Entity, string>> che può essere sia una proprietà o di accesso di una proprietà nidificatoCome ri-avvolgere un Expression Linq albero

y => y.SearchColumn 

o

y => y.SubItem.SubColumn 

Sto costruendo un albero di espressione in modo dinamico e vorrei ottenere un InvokeExpression come questo

x => x.SearchColumn.Contains("foo"); 
x => x.Sub.SearchColumn.Contains("foo"); 

Sono quasi sicuro di poter scartare l'Expr corpo ESSIONE, quindi applicare parzialmente l'x ParameterExpression ad esso, ma non riesco a capire i incantesimi magici per rendere questo accada

Così 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; 
    } 

Edit: Here is the full thing I'm trying to do along with the error

risposta

3

Devi fare due cose: prima puoi usare lo stesso accessor.Body, ma farà riferimento al parametro errato, mentre ne hai creato uno nuovo. Secondo, è necessario scrivere l'originale ExpressionVisitor che sostituirà l'utilizzo di ParameterExpression originale in un nuovo creato, quindi l'espressione del risultato verrà compilata correttamente.

Se non è necessario creare nuovo ParameterExpression, è possibile utilizzare lo stesso accessor.Body e inviare nuovamente l'originale ParameterExpression a un nuovo albero.

Così qui è la mia copia di lavoro test 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 funziona solo se si dispone di un solo ParameterExpression nella query originale, altrimenti visitatore deve differenziarli

UPDATE

Ecco la mia risposta di lavoro con la vostra piena esempio in aggiornamento - https://dotnetfiddle.net/MXP7wE

+1

Sì.La cosa importante da riconoscere è che gli alberi di espressione sono immutabili, quindi per poter modificare un pezzo di un albero di espressioni è necessario creare una copia completa di quell'albero dell'espressione con le parti chiave modificate, che è lo scopo di un ExpressionVisitor. – StriplingWarrior

+1

Santa merda, fantastico Sergey. Sono contento di sapere che stavo vicino :) –

1

Hai solo bisogno di una correzione di un paio di cose:

  • tipo di ritorno del metodo dovrebbe essere Expression<Func<T, bool>>.
  • Il primo parametro su Expression.Call() deve essere semplicemente accessor.Body.
  • Il parametro ParameterExpression per la chiamata del metodo Expression.Lambda<Func<T, bool>>() deve essere impostato semplicemente dal parametro accessor.

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] 
    ); 
} 
+0

Hai dato un'occhiata alla mia modifica? [Ecco la cosa completa che sto cercando di fare] (https://dotnetfiddle.net/wo1Cfi) –