2010-02-09 16 views
12

ho seguito questa discussione: link textAppend un'espressione

Jason dà un esempio:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters); 
} 

e il suo utilizzo come tale:

Expression<Func<Client, bool>> clientWhere = c => true; 
if (filterByClientFName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName); 
} 
if (filterByClientLName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName); 
} 

Ho una tabella ordini e ho seguito l'esempio sopra, cambiando i nomi delle colonne, e ottengo l'errore simile che il post creatore aveva

L'operatore binario AndAlso non è definito per i tipi 'System.Func 2[Models.Order,System.Boolean]' and 'System.Func 2 [Models.Order, System.Boolean]'.

Qualcuno ha qualche idea su cosa mi manca?

AGGIORNAMENTO:

Eric, ho ulteriormente seguito ciò che l'utente del post precedente stava chiedendo, qui link text

L'utente ha questo

Expression<Func<Client, bool>> clientWhere = c => true; 
Expression<Func<Order, bool>> orderWhere = o => true; 
Expression<Func<Product, bool>> productWhere = p => true; 

if (filterByClient) 
{ 
    clientWhere = c => c.ClientID == searchForClientID; 
} 

Ora, se si dovesse avere varie condizioni in filterByClient, dire che ha o clientid e/o qualche altro nome di colonna, come si costruisce l'espressione clientWhere?

risposta

29

si sta tentando di costruire un albero di espressione che rappresenta questo:

c => true && c.ClientFName == searchForClientFName 

In realtà si sta costruendo un albero di espressione che rappresenta questo:

c => c=> true && c => c.ClientFName == searchForClientFName 

che non ha senso a tutti.

Ora, si potrebbe ingenuamente pensare che questo funzionerà:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
// NOTICE: Combining BODIES: 
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters); 
} 

che produrrebbe nel tuo caso qualcosa che rappresenta

c => true && c.ClientFName == searchForClientFName 

che guarda a destra. Ma in realtà questo è fragile. Supponi di avere

... d => d.City == "London" ... 
... c => c.ClientName == "Fred Smith" ... 

e hai usato questo metodo per combinarli. Otterrai un oggetto che rappresenta

c => d.City == "London" && c.ClientName == "Fred Smith" 

Cosa diavolo ci farebbe lì?

Inoltre, i parametri sono abbinati per oggetto l'identità, non dal nome del parametro.Se si esegue questa operazione

... c => c.City == "London" ... 
... c => c.ClientName == "Fred Smith" ... 

e combinarle in

c => c.City == "London" && c.ClientName == "Fred Smith" 

sei nella stessa barca; la "c" in "c.City" è un diverso c rispetto agli altri due.

Quello che è effettivamente necessario fare è fare un terzo oggetto parametro , sostituto che nei corpi di entrambe le espressioni lambda per ogni occorrenza di loro parametri, e poi costruire un albero nuova espressione lambda dalla risultante corpi sostituiti.

È possibile creare un motore di sostituzione scrivendo un visitatore che passa sopra il corpo dell'albero dell'espressione, riscrivendolo nel modo desiderato.

+4

Sembra una risposta intelligente alla domanda perché il problema esiste, ma non hai davvero dato una soluzione in modo che uno possa trarre beneficio dal tuo post ... –

+7

@ Michael: Quindi ti invito a scrivere una risposta che preferiresti. –

+0

Ho fatto qualcosa di simile a ciò che Eric suggerisce qui: http://stackoverflow.com/questions/14248674/system-linq-expressions-binding-lambdaexpression-inputs-at-runtime Potrebbe essere utile. –

11

Mi è stato difficile capire hvd answer così ho creato del codice per spiegarlo in un modo diverso. hvd dovrebbe avere il merito di suggerire l'ExpressionVisitor. Non riuscivo a capire l'esempio nel contesto delle funzioni di input di tipo Linq to X che stavo usando.

Spero che questo aiuti qualcun altro a venire alla domanda da quella prospettiva.

Inoltre, ho creato il codice di combinazione come metodi di estensione per renderlo un po 'più facile da usare.


using System; 
using System.Collections.Generic; 
using System.Linq.Expressions; 

namespace ConsoleApplication3 
{ 

    class Program 
    { 

     static void Main(string[] args) 
     { 

      var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy"); 

      Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" })); 
      Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" })); 

      Console.ReadLine(); 
     } 

     public class FullName 
     { 
      public string FirstName { get; set; } 
      public string LastName { get; set; } 
     } 

     public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2) 
     { 
      return func1.CombineWithAndAlso(func2).Compile(); 
     } 
    } 

    public static class CombineExpressions 
    { 
     public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) 
     { 
      return Expression.Lambda<Func<TInput, bool>>(
       Expression.AndAlso(
        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), 
       func1.Parameters); 
     } 

     public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) 
     { 
      return Expression.Lambda<Func<TInput, bool>>(
       Expression.AndAlso(
        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), 
       func1.Parameters); 
     } 

     private class ExpressionParameterReplacer : ExpressionVisitor 
     { 
      public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters) 
      { 
       ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>(); 
       for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++) 
        ParameterReplacements.Add(fromParameters[i], toParameters[i]); 
      } 

      private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; } 

      protected override Expression VisitParameter(ParameterExpression node) 
      { 
       ParameterExpression replacement; 
       if (ParameterReplacements.TryGetValue(node, out replacement)) 
        node = replacement; 
       return base.VisitParameter(node); 
      } 
     } 
    } 
} 
+0

Grazie amico! Questa dovrebbe essere la risposta accettata! – apostolov

+0

Ha funzionato. Molte grazie. :-) –

0

se ne avete bisogno ho creato una piccola biblioteca fluente per creare funzioni lambda al volo, senza affrontare direttamente con System.Linq.Expressions. E può facilmente gestire il tipo di situazione. Tanto per fare un esempio:

static void Main(string[] args) 
{ 
    var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName); 
    var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName); 

    Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy"); 

    var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"}; 
    Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck)); 
    toCheck = new FullName {FirstName = "Cat", LastName = "Boy"}; 
    Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck)); 

    Console.ReadLine(); 
} 

Il GetComparer metodi cercano per la proprietà passò come espressione e trovano ho per ottenere il suo valore, allora si costruisce una nuova espressione che gestirà il comparaison.

Alla fine le due funzioni vengono valutate chiamando la funzione "combinata".

Se avete bisogno di più controlli si potrebbe usare un array e iterare su di essa all'interno del "lambda combinato"

Il codice e la documentazione per la libreria sono qui: Kendar Expression Builder mentre il pacchetto NuGet è qui: Nuget Expression Builder

0

Ho provato a implementare questo tipo di cose. Mi ci è voluto un giorno per scoprirlo. La mia soluzione si basa sul filtro in un ciclo basato su una matrice di predicato. Come nota, è totalmente generico e basato su Reflection perché le sole informazioni su classe e campo sono String. Per semplificare, chiamo direttamente la classe Model ma in un progetto dovresti andare da un controler che sta chiamando il Modello.

Quindi qui si va: La parte del modello dove T è un generico nella classe

public class DALXmlRepository<T> where T : class 
    { 
    public T GetItem(Array predicate) 
    { 
     IQueryable<T> QueryList = null; 

     QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0)); 
     for (int i = 1; i < predicate.GetLength(0); i++) 
     { 
      QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i)); 
     } 

     if (QueryList.FirstOrDefault() == null) 
      throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found."); 
     return QueryList.FirstOrDefault(); 
    } 
    } 

Ora il LambdaExpression Builder, è uno di base (di tipo stringa o altro), si può migliorare con più functionnality:

private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue) 
    { 
     LambdaExpression lambda = null; 

     Expression Criteria = null; 

     Random r = new Random(); 
     ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString()); 

     if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string)) 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null); 
      //Type du champ recherché 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(FieldValue, propType); 
      Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null); 
      Criteria = Expression.Equal(LefttoUpper, RighttoUpper); 
     } 
     else 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType); 

      Criteria = Expression.Equal(left, right); 
     } 

     lambda = Expression.Lambda(Criteria, predParam); 
     return lambda; 
    } 

Ora la funzione chiamante:

public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter) 
    { 
     //Get the type 
     Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel"); 
     Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType(type); 
     //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML); 
     ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) }); 
     IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null }); 

     //Building the string type Expression<func<T,bool>> to init the array 
     Type FuncType = typeof(Func<,>).MakeGenericType(type ,typeof(bool)); 
     Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType); 
     Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count); 

     MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() }); 

     if (method == null) 
      throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name); 

     int j = 0; 
     IDictionaryEnumerator criterias = FieldFilter.GetEnumerator(); 
     criterias.Reset(); 
     while (criterias.MoveNext()) 
     { 
      if (!String.IsNullOrEmpty(criterias.Key.ToString())) 
      { 
       lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j); 
      } 
      else 
      { 
       throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString()); 
      } 
      j++; 
     } 

     Object item = method.Invoke(DalInstance, new object[] { lambda }); 
     } 

L'argomento è: Stringa Entità: nome della classe di entità. XMLContext: è l'unità di lavoro del repository, argomento che utilizzo per inizializzare la classe Model Hashtable FieldsNameToGet: indice/valore dell'elenco del campo che si desidera recuperare FieldFilter Hashtable: la chiave/valore con FieldName/Contenuto utilizzato per rendere l'espressione Lambda

Buona fortuna.