2010-02-09 18 views
12

he seguido este hilo: link textAnexar a una expresión

Jason da un ejemplo:

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

y su uso como tal:

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); 
} 

tengo una tabla de pedidos y i seguido el ejemplo anterior, cambiando los nombres de las columnas, y obtengo el error similar que el creador de la publicación tenía

El operador binario AndAlso no está definido para los tipos 'System.Func 2[Models.Order,System.Boolean]' and 'System.Func 2 [Models.Order, System.Boolean]'.

¿Alguien tiene alguna idea sobre lo que me falta?

ACTUALIZADO:

Eric, seguido aún más lo que el usuario de la entrada anterior estaba pidiendo, aquí link text

El usuario ha este

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

Ahora bien, si se fuera a tener diferentes condiciones en filterByClient, digamos que tiene clientid y/o algún otro nombre de columna, ¿cómo se generaría la expresión clientWhere?

Respuesta

29

usted está tratando de construir un árbol de expresión que representa esto:

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

En realidad se está construyendo un árbol de expresión que representa esto:

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

que no tiene sentido en absoluto.

Ahora, es posible que ingenuamente pensar que esto va a funcionar:

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); 
} 

que produciría en su caso, algo que representa

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

que se ve a la derecha. Pero, de hecho, esto es frágil. Supongamos que tiene

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

y usó este método para combinarlos. Obtendrá un objeto que representa

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

¿Qué diablos está haciendo allí?

Además, los parámetros se emparejan por la identidad del objeto, no por nombre de parámetro.Si hace esto

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

y combinarlos en

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

estás en el mismo barco; la "c" en "c.City" es diferente c que las otras dos.

Lo que realmente necesita hacer es hacer un tercer objeto parámetro , sustituto en los cuerpos de los dos lambdas para cada ocurrencia de sus parámetros, y luego construir un árbol nueva expresión lambda de la resultante cuerpos sustituidos.

Puede construir un motor de sustitución escribiendo a un visitante que pasa sobre el cuerpo del árbol de expresiones, reescribiéndolo a medida que avanza.

+4

Suena como una respuesta inteligente para la pregunta de por qué existe el problema, pero realmente no ha dado una solución para que uno se pueda beneficiar de su publicación ... –

+7

@Michael: Entonces los invito a escribir una respuesta que usted preferiría. –

+0

Hice algo similar a lo que sugiere Eric aquí: http://stackoverflow.com/questions/14248674/system-linq-expressions-binding-lambdaexpression-inputs-at-runtime Puede que le resulte útil. –

11

Me resultó difícil entender hvd's answer, así que creé un código para explicarlo de otra manera. hvd debería obtener el crédito por sugerir el ExpressionVisitor. Simplemente no pude entender el ejemplo en el contexto de las funciones de entrada tipo Linq a X que estaba usando.

Espero que esto ayude a alguien más a llegar a la pregunta desde esa perspectiva.

Además, he creado el código de combinación como métodos de extensión para que sea un poco más fácil de usar.


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

Gracias hombre! ¡Esta debería ser la respuesta aceptada! – apostolov

+0

Funcionó. Muchas gracias. :-) –

0

Si necesita que he creado una pequeña biblioteca con fluidez para crear funciones lambda sobre la marcha sin enfrentarse directamente con System.Linq.Expressions. Y puede manejar fácilmente el tipo de situación. Sólo para dar un ejemplo:

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(); 
} 

El GetComparer métodos buscan la propiedad pasa como expresión y encontrar ho para obtener su valor, entonces se construye una nueva expresión que controlará el comparaison.

Al final, las dos funciones se evalúan llamando a la función "combinada".

Si necesita más verificaciones se puede utilizar una matriz y iterar sobre ella dentro de la "lambda combinado"

El código y la documentación de la biblioteca están aquí: Kendar Expression Builder Mientras que el paquete Nuget está aquí: Nuget Expression Builder

0

Intenté implementar este tipo de cosas. Me llevó un día averiguarlo. Mi solución se basa en el filtro en un bucle basado en una matriz de predicado. Como nota, es Reflejo totalmente genérico y basado porque la única información sobre clase y campo es Cadena. Para hacerlo simple, llamo directamente a la clase Model pero en un proyecto debe ir por un controlador que está llamando al Modelo.

Así que aquí vamos: la parte del modelo, donde T es un genérico en la clase

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(); 
    } 
    } 

Ahora el LambdaExpression constructor, que es una base (con el tipo de cuerda o algo más), se puede mejorar con más Funcionalidad:

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

Ahora la función de llamada:

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 }); 
     } 

El argumento es: Entidad de cadena: nombre de clase de entidad. XMLContext: es la unidad de trabajo del repositorio, argumento que uso para inicializar la clase Model Hashtable FieldsNameToGet: Índice/valor de la lista del campo que quiero recuperar Hashtable FieldFilter: la clave/Valor con FieldName/Contenido utilizado para hacer la expresión Lambda

Buena suerte.