linq a entidades dinámicas donde construir desde lambdas

c# entity-framework expression-trees lambda linq

Pregunta

tengo un conjunto de lambdas como esta

t => t.FirstName
t => t.LastName
t => t.Profession

Me gustaría encontrar una manera de construir una expresión que pueda usarse en una declaración Where en Linq to Entities donde estas lambdas se comparan con un valor utilizando string.contains

// a filter is definded by a lambda and the string to compare it with   
var filters = new Dictionary<Expression<Func<Person, string>>, string>();
filters.Add(t => t.FirstName, "Miller");
filters.Add(t => t.Profession, "Engineer");
var filterConstraints = BuildFilterExpression(t => t, filters);
Entities.Persons.Where(filterConstraints).ToList();

public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
  List<Expression> expressions = new List<Expression>();

  var stringType = typeof(string);
  var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });

  foreach (var constraint in constraints)
  {
    var equalsExpression = (Expression)Expression.Call(constraint.Key.Body, containsMethod, Expression.Constant(constraint.Value, stringType));
    expressions.Add(equalsExpression);
  }

  var body = expressions.Aggregate((accumulate, equal) => Expression.And(accumulate, equal));

  ParameterExpression p = constraints.First().Key.Parameters.First();
  return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Supongo que estoy haciendo algo terriblemente mal en la construcción del árbol de expresiones porque obtengo la siguiente excepción: Excepción de operación no válida: el parámetro 't' no estaba vinculado en la expresión de consulta LINQ to Entities especificada.

¿Alguien sabe cómo solucionar este problema?

Respuesta aceptada

En realidad estás muy cerca. El problema es que los objetos de parámetros que tienen el mismo nombre y tipo, no son técnicamente "iguales".

var b = Expression.Parameter(typeof(string), "p") == 
    Expression.Parameter(typeof(string), "p");
//b is false

Así que el parámetro de la lambda que creas es el parámetro de la primera expresión que tomas como entrada. Los parámetros utilizados en el cuerpo de todas las demás expresiones son parámetros diferentes , y no se dan como parámetros a la lambda, por lo que el error se debe a eso.

La solución es bastante simple. Solo necesita reemplazar todas las instancias de todos los demás parámetros con el parámetro real que desea usar.

Aquí hay un método auxiliar (que usa una clase auxiliar) que toma todas las instancias de una expresión en alguna expresión y la reemplaza por otra:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Ahora solo llamamos a eso una vez en cada cuerpo, reemplazando en un parámetro común:

public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(
    Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
    List<Expression> expressions = new List<Expression>();

    var stringType = typeof(string);
    var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });

    var parameter = Expression.Parameter(typeof(TElement));

    foreach (var constraint in constraints)
    {
        var equalsExpression = (Expression)Expression.Call(
            constraint.Key.Body.Replace(constraint.Key.Parameters[0], parameter),
            containsMethod, Expression.Constant(constraint.Value, stringType));
        expressions.Add(equalsExpression);
    }

    var body = expressions.Aggregate((accumulate, equal) =>
        Expression.And(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, parameter);
}

Respuesta popular

Cerca. Desafortunadamente si echa un vistazo dentro de cada una de sus propiedades lambdas, por ejemplo ...

t => t.FirstName 
t => t.LastName

Usted encontrará que son cada uno de Expression.Property . Sin embargo, cada uno de ellos tiene un parámetro de Expression.Parameter diferente. Desea utilizar un ExpressionVisitor para reemplazar el PropertyExpression.Parameter con la misma INSTANCIA de Expression.Parameter Y usarlo con Expression.Lambda .

La excepción Excepción de operación no válida: el parámetro 't' no estaba vinculado en la expresión de consulta LINQ a Entidades especificada. significa que tiene ParameterExpression s en el cuerpo de su lambda que no están en la matriz de parámetros de lambda.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow