Pregunta

Estoy usando mis propios métodos de extensión de IQueryable <> para crear consultas encadenables como FindAll (). FindInZip (12345) .NameStartsWith ( "XYZ"). OrderByHowIWantIt (), etc., que a continuación, en ejecución diferida crea una sola consulta basado en mi cadena métodos de extensión.

El problema con esto, sin embargo, es que todos ¿Dónde está en la cadena de extensión (FindXYZ, FindInZip etc.) combinará siempre como Y lo que significa que no puedo hacer algo como esto:

FindAll (). FirstNameStartsWith ( "X"). OrLastNameStartsWith ( "Z") porque no sé cómo puedo inyectar el o en una separada donde método.

Cualquier idea de cómo puedo solucionar esto?


adicional; Hasta ahora entiendo cómo la cadena de expresiones como O si yo envuelvo (por ejemplo CompileAsOr (FirstNameStartsWith ( "A"). LastNameStartsWith ( "Z"). OrdenarPor (..))

Lo que estoy tratando de hacer, aunque es un poco más complicado (y PredicateBuilder no ayuda aquí ..) en la que yo quiero un IQueryable más adelante para acceder básicamente el caso de las condiciones que se establecieron antes sin tener que envuelven a crear la O entre ellos.

A medida que cada método de extensión devuelve IQueryable <> entiendo que debe tener el conocimiento sobre el estado actual de condiciones de consulta en algún lugar, lo que me lleva a creer que debe haber alguna manera automatizada o la creación de una o en todas las condiciones previas Donde sin tener que envolver lo que quiere usando OR.

¿Fue útil?

Solución

Estoy asumiendo las diferentes partes de la consulta sólo se conocen en tiempo de ejecución, es decir, no se puede simplemente usar || en un where ...

Una de las opciones es perezosa Concat - pero esto tiende a conducir a una mala TSQL, etc; Sin embargo, tiendo a estar inclinado a escribir Expressions personalizados en su lugar. El enfoque a adoptar depende de lo que el proveedor es, como LINQ a SQL es compatible con diferentes opciones para EF (por ejemplo) - que tiene un impacto real aquí (ya que no puede utilizar sub-expresiones con EF). ¿Puede usted decirnos qué?


Aquí hay un código que debe trabajar con LINQ a SQL; si se construye una matriz (o lista, y llamar .ToArray()) de expresiones, que debería funcionar bien; ejemplo de ello es LINQ a objetos, pero aún así debería funcionar:

    static void Main()
    {
        var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();

        var predicates = new List<Expression<Func<int, bool>>>();
        predicates.Add(i => i % 3 == 0);
        predicates.Add(i => i >= 8);           

        foreach (var item in data.WhereAny(predicates.ToArray()))
        {
            Console.WriteLine(item);
        }
    }

    public static IQueryable<T> WhereAny<T>(
        this IQueryable<T> source,
        params Expression<Func<T,bool>>[] predicates)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return source.Where(x => false); // no matches!
        if (predicates.Length == 1) return source.Where(predicates[0]); // simple

        var param = Expression.Parameter(typeof(T), "x");
        Expression body = Expression.Invoke(predicates[0], param);
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return source.Where(lambda);
    }

Otros consejos

PredicateBuilder<T> . Es probablemente lo que quiere.

    List<string> fruits =
        new List<string> { "apple", "passionfruit", "banana", "mango",
               "orange", "blueberry", "grape", "strawberry" };

    var query = fruits.AsQueryable();

    // Get all strings whose length is less than 6.
    query = query.Where(fruit => fruit.Length < 6);

    // Hope to get others where length is more than 8.  But you can't, they're gone.
    query = query.Where(fruit => 1 == 1 || fruit.Length > 8);

    foreach (string fruit in query)
        Console.WriteLine(fruit);

En un mundo ideal, yo personalmente creo que los operadores || y && serían los más simple y fácil de leer. Sin embargo, no se compilará.

  

operador '||' no se puede aplicar a operandos de tipo 'Expression<Func<YourClass,bool>>' y 'Expression<Func<YourClass,bool>>'

Por lo tanto utilizar un método de extensión para esto. En su ejemplo se vería así:  .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)).

En lugar de:

.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now)).

ejemplo Expresión:

private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
      return post => post.PostedOn >= cutoffDate;
};

Método de extensión:

public  static  class PredicateExtensions
{
     ///  <summary>
     /// Begin an expression chain
     ///  </summary>
     ///  <typeparam id="T""></typeparam>
     ///  <param id="value"">Default return value if the chanin is ended early</param>
     ///  <returns>A lambda expression stub</returns>
     public  static Expression<Func<T,  bool>> Begin<T>(bool value =  false)
    {
         if (value)
             return parameter =>  true;  //value cannot be used in place of true/false

         return parameter =>  false;
    }

     public  static Expression<Func<T,  bool>> And<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.AndAlso);
    }

     public  static Expression<Func<T,  bool>> Or<T>(this Expression<Func<T,  bool>> left, Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.OrElse);
    }

     #region private

     private  static Expression<Func<T,  bool>> CombineLambdas<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right, ExpressionType expressionType)
    {
         //Remove expressions created with Begin<T>()
         if (IsExpressionBodyConstant(left))
             return (right);

        ParameterExpression p = left.Parameters[0];

        SubstituteParameterVisitor visitor =  new SubstituteParameterVisitor();
        visitor.Sub[right.Parameters[0]] = p;

        Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
         return Expression.Lambda<Func<T,  bool>>(body, p);
    }

     private  static  bool IsExpressionBodyConstant<T>(Expression<Func<T,  bool>> left)
    {
         return left.Body.NodeType == ExpressionType.Constant;
    }

     internal  class SubstituteParameterVisitor : ExpressionVisitor
    {
         public Dictionary<Expression, Expression> Sub =  new Dictionary<Expression, Expression>();

         protected  override Expression VisitParameter(ParameterExpression node)
        {
            Expression newValue;
             if (Sub.TryGetValue(node,  out newValue))
            {
                 return newValue;
            }
             return node;
        }
    }

     #endregion
} 

Un artículo muy bueno sobre LINQ consultas extendiendo las expresiones. También la fuente del método de extensión que uso.

https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top