Varias cláusulas WHERE con métodos de extensión LINQ

79

Tengo una consulta LINQ que se parece a lo siguiente:

DateTime today = DateTime.UtcNow;
var results = from order in context.Orders
              where ((order.OrderDate <= today) && (today <= order.OrderDate))
              select order;

Estoy tratando de aprender / entender LINQ. En algunos casos, necesito agregar dos cláusulas WHERE adicionales. En un esfuerzo por hacer esto, estoy usando:

if (useAdditionalClauses)
{
  results = results.Where(o => o.OrderStatus == OrderStatus.Open)  // Now I'm stuck.
}

Como puede ver, sé cómo agregar una cláusula WHERE adicional. Pero, ¿cómo agrego varios? Por ejemplo, me gustaría agregar

WHERE o.OrderStatus == OrderStatus.Open AND o.CustomerID == customerID

a mi consulta anterior. ¿Cómo hago esto usando métodos de extensión?

¡Gracias!

user609886
fuente

Respuestas:

151

Dos caminos:

results = results.Where(o => (o.OrderStatus == OrderStatus.Open) &&
                             (o.CustomerID == customerID));

o:

results = results.Where(o => (o.OrderStatus == OrderStatus.Open))
                 .Where(o => (o.CustomerID == customerID));

Normalmente prefiero lo último. Pero vale la pena perfilar el servidor SQL para verificar la ejecución de la consulta y ver cuál funciona mejor para sus datos (si hay alguna diferencia).

Una nota sobre el encadenamiento de .Where()métodos: puede encadenar todos los métodos LINQ que desee. Métodos como .Where()no se ejecutan realmente en la base de datos (todavía). Ellos difieren de ejecución hasta que los resultados reales se calculan (como con una .Count()o una .ToList()). Entonces, a medida que encadena varios métodos (más llamadas a .Where(), tal vez uno .OrderBy()o algo en ese sentido, etc.), construyen lo que se llama un árbol de expresión . Todo este árbol es lo que se ejecuta contra la fuente de datos cuando llega el momento de evaluarla.

David
fuente
2
Me siento tonto sin saber que podría hacer esto .. Me acabas de salvar de tanto código espagueti.
ledgeJumper
Gracias, eso me ayudó. Pero, ¿es también posible que active cualquiera de las cláusulas where dependiendo de una determinada variable? @David
Muhammad Ashikuzzaman
¿Puedes usar esto con una cláusula de selección al final?
@New_Coder: Por supuesto. La cláusula .Where () no cambia el tipo de retorno.
David
es extraño porque cuando hago esto: List <cadena> rutas = db.ClientStatement_Inventory .Where (x => (x.statementYear == yea)) .Where (x => (x.statementMonth == mon)) .Select ( c => c.statementPath) .ToList (); No funciona. pero si solo tengo 1 cláusula where, consulta mi base de datos.
24

Puedes seguir encadenándolos como lo has hecho.

results = results.Where (o => o.OrderStatus == OrderStatus.Open);
results = results.Where (o => o.InvoicePaid);

Esto representa un AND.

Bryan Boettcher
fuente
Tú, y otros, también me ganaste, pero esta es probablemente la forma más legible de hacerlo.
Gato Schroedinger
5
Se repiten las cláusulas donde se agregan a la consulta con un operador "y" en el medio.
linkerro
Probablemente esta no sea la solución 'más limpia', pero en mi caso es la única que funcionó hasta ahora. Tuve que agregar cláusulas 'donde' según las selecciones en la interfaz de usuario.
DJ van Wyk
1
¿Hay alguna manera de hacer esto de modo que el "Dónde está" ed?
EK_AllDay
11

Si trabaja con datos en memoria (lea "colecciones de POCO"), también puede apilar sus expresiones juntas usando PredicateBuilder así:

// initial "false" condition just to start "OR" clause with
var predicate = PredicateBuilder.False<YourDataClass>();

if (condition1)
{
    predicate = predicate.Or(d => d.SomeStringProperty == "Tom");
}

if (condition2)
{
    predicate = predicate.Or(d => d.SomeStringProperty == "Alex");
}

if (condition3)
{
    predicate = predicate.And(d => d.SomeIntProperty >= 4);
}

return originalCollection.Where<YourDataClass>(predicate.Compile());

La fuente completa de lo mencionado se PredicateBuilderencuentra a continuación (pero también puede consultar la página original con algunos ejemplos más):

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

public static class PredicateBuilder
{
  public static Expression<Func<T, bool>> True<T> ()  { return f => true;  }
  public static Expression<Func<T, bool>> False<T> () { return f => false; }

  public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
                                                      Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
  }

  public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                       Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
  }
}

Nota : He probado este enfoque con el proyecto de biblioteca de clases portátil y tengo que usarlo .Compile()para que funcione:

Donde (predicado .Compile () );

Sevenate
fuente
¿Hay alguna razón por la que esto no funcione con Entity Framework LINQ?
Ciantic
También funciona bien con EF Core para mí. El predicado resultante se traduce correctamente a SQL.
Thomas Hilbert
5

Seguramente:

if (useAdditionalClauses) 
{ 
  results = 
    results.Where(o => o.OrderStatus == OrderStatus.Open && 
    o.CustomerID == customerID)  
} 

O simplemente otra .Where()llamada como esta (aunque no sé por qué querría hacerlo, a menos que esté dividida por otra variable de control booleana):

if (useAdditionalClauses) 
{ 
  results = results.Where(o => o.OrderStatus == OrderStatus.Open).
    Where(o => o.CustomerID == customerID);
} 

O otra reasignación a results: `results = results.Where ( blah ).

Andras Zoltan
fuente
2

puede usar && y escribir todas las condiciones en la misma cláusula where, o puede .Where (). Where (). Where () ... y así sucesivamente.

Josh C.
fuente
1
results = context.Orders.Where(o => o.OrderDate <= today && today <= o.OrderDate)

La selección no es necesaria porque ya está trabajando con un pedido.

Caballero
fuente
0

Simplemente use el &&operador como lo haría con cualquier otra declaración que necesite para hacer lógica booleana.

if (useAdditionalClauses)
{
  results = results.Where(
                  o => o.OrderStatus == OrderStatus.Open 
                  && o.CustomerID == customerID)     
}
cadrell0
fuente