2012-02-10 11 views
11

Estoy intentando construir un árbol de expresiones mediante programación.Excepción utilizando los métodos de expresión OrElse y AndAlso

que tengo en la entrada, una lista de clases de condición que tienen la siguiente forma:

public class Filter 
{ 
    public string field { get; set; } 
    public string operator { get; set; } 
    public string value { get; set; } 
} 

Cuando construyo el objeto Expression creo una Expression para cada condición de la siguiente manera

foreach (Filter sf in rules) { 
    Expression ex = sf.ToExpression(query); 
    if (mainExpression == null) { 
     mainExpression = ex; 
    } 
    else { 
     if (logicalCondition == "AND") { 
      mainExpression = Expression.And(mainExpression, ex); 
     } 
     else if (logicalCondition == "OR") { 
      mainExpression = Expression.Or(mainExpression, ex); 
     } 
    } 
} 

El método Filter.ToExpression() se implementa así

public override Expression ToExpression(IQueryable query) { 
    ParameterExpression parameter = Expression.Parameter(query.ElementType, "p"); 
    MemberExpression memberAccess = null; 
    foreach (var property in field.Split('.')) 
     memberAccess = MemberExpression.Property(memberAccess ?? (parameter as Expression), property); 
    ConstantExpression filter = Expression.Constant(Convert.ChangeType(value, memberAccess.Type)); 
    WhereOperation condition = (WhereOperation)StringEnum.Parse(typeof(WhereOperation), operator); 
    LambdaExpression lambda = BuildLambdaExpression(memberAccess, filter, parameter, condition, value); 
    return lambda; 
} 

Todo funciona cuando tengo una sola condición, pero cuando intento de combinar las expresiones que utilizan uno de los And, Or, AndAlso, OrElse métodos estáticos recibo un InvalidOperationException que dice:

El operador binario o no se define para los tipos 'System.Func 2[MyObject,System.Boolean]' and 'System.Func 2 [MyObject, System.Boolean]'.

Me estoy poniendo un poco confundido. ¿Puede alguien explicar mejor los motivos de la excepción y sugerir una solución?

Muchas gracias!

Respuesta

23

Está combinando a => a == 3 y a => a == 4 en (a => a == 3) || (a => a == 4), pero en su lugar debería intentar que sea a => (a == 3 || a == 4). Esto no es demasiado difícil de hacer manualmente, pero someone has done it for you already. Busque "Combinar expresiones".

Editar: según lo solicitado, un ejemplo simple de cómo hacer esto manualmente.

Editar 2: usa ExpressionVisitor que es nuevo en .NET 4, pero at MSDN you can find a usable implementation for earlier versions. Supongo que el código de MSDN no califica como "de terceros" para sus propósitos. Solo necesita cambiar el método protected virtual Expression Visit(Expression exp) al public. Y como Enumerable.Zip no está disponible para usted y no es necesario, ya no está disponible.

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

namespace DemoApp 
{ 
    <include ExpressionVisitor definition here for .NET 3.5> 

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

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Expression<Func<int, bool>> exprA = a => a == 3; 
      Expression<Func<int, bool>> exprB = b => b == 4; 
      Expression<Func<int, bool>> exprC = 
       Expression.Lambda<Func<int, bool>>(
        Expression.OrElse(
         exprA.Body, 
         new ExpressionParameterReplacer(exprB.Parameters, exprA.Parameters).Visit(exprB.Body)), 
        exprA.Parameters); 
      Console.WriteLine(exprA.ToString()); 
      Console.WriteLine(exprB.ToString()); 
      Console.WriteLine(exprC.ToString()); 
      Func<int, bool> funcA = exprA.Compile(); 
      Func<int, bool> funcB = exprB.Compile(); 
      Func<int, bool> funcC = exprC.Compile(); 
      Debug.Assert(funcA(3) && !funcA(4) && !funcA(5)); 
      Debug.Assert(!funcB(3) && funcB(4) && !funcB(5)); 
      Debug.Assert(funcC(3) && funcC(4) && !funcC(5)); 
     } 
    } 
} 
+0

Hola, gracias por tu respuesta. No puedo usar código de terceros para resolver este problema. ¿Podría explicar mejor cuál sería la forma de hacerlo manualmente? ¡Gracias de nuevo! – Lorenzo

+0

@Lorenzo Seguro, he agregado un programa basado en las dos expresiones que utilicé como ejemplo. – hvd

+0

Hola, he intentado implementar su solución. Comprendí que el 'ExpressionVisitor' proviene de las fuentes de LinqKit y pude ver cómo funciona. La pregunta ahora es: ¿de dónde viene el método 'Zip' de' IEnumerable '? Estoy usando .NET 3.5 y no puedo encontrar ese método :( – Lorenzo

Cuestiones relacionadas