Consultas condicionales de Linq

92

Estamos trabajando en un visor de registros. El uso tendrá la opción de filtrar por usuario, gravedad, etc. En los días de Sql agregaría a la cadena de consulta, pero quiero hacerlo con Linq. ¿Cómo puedo agregar condicionalmente cláusulas where?

sgwill
fuente

Respuestas:

156

si solo desea filtrar si se pasan ciertos criterios, haga algo como esto

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

Si lo hace de esta manera, permitirá que su árbol de expresiones sea exactamente lo que desea. De esa manera, el SQL creado será exactamente lo que necesita y nada menos.

Darren Kopp
fuente
2
Hola ¿Tiene alguna sugerencia para hacer que las cláusulas where sean OR en lugar de AND ...?
Jon H
1
Sí ... es un poco difícil de hacer. Lo mejor que he visto es a través del patrón de especificación y tirando del predicado a la especificación y luego llamando a la especificación. O (someOtherSpecification). Básicamente, tienes que escribir un poco tu propio árbol de expresión. Código de ejemplo y explicación aquí: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
Tengo una pregunta estúpida, si estos registros se adquieren de la base de datos, ¿obtendremos todos los registros y luego los filtraremos en la memoria? Si es así, ¿cómo puedo pasar las condiciones a la base de datos?
Ali Umair
no los está filtrando en la memoria. está creando una consulta y enviando todas las condiciones en la base de datos (al menos para la mayoría de los proveedores de linq-to-x)
Darren Kopp
recibiendo este errorLINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
Ali Umair
22

Si necesita filtrar la base en una lista / matriz, use lo siguiente:

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }
Carlos
fuente
3
Esta es, con mucho, la mejor y más correcta respuesta. El condicional || solo compara la primera parte y salta la segunda si la primera parte es verdadera ... ¡muy bien hecho!
Serj Sagan
1
Esta construcción incluye la parte 'o' de la expresión en la consulta SQL generada. La respuesta aceptada generará declaraciones más eficientes. Dependiendo de las optimizaciones del proveedor de datos, por supuesto. LINQ-to-SQL puede tener una mejor optimización, pero LINQ-to-Entities no.
Suncat2000
20

Terminé usando una respuesta similar a la de Daren, pero con una interfaz IQueryable:

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

Eso construye la consulta antes de llegar a la base de datos. El comando no se ejecutará hasta .ToList () al final.

sgwill
fuente
14

Cuando se trata de linq condicional, me gustan mucho los filtros y el patrón de tuberías.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Básicamente, crea un método de extensión para cada caso de filtro que incluye IQueryable y un parámetro.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}
Lars Mæhlum
fuente
8

Resolví esto con un método de extensión para permitir que LINQ se habilite condicionalmente en medio de una expresión fluida. Esto elimina la necesidad de dividir la expresión con ifdeclaraciones.

.If() método de extensión:

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

Esto le permite hacer esto:

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

Aquí también hay una IEnumerable<T>versión que manejará la mayoría de las otras expresiones LINQ:

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }
Ryan
fuente
4

Otra opción sería utilizar algo como el PredicateBuilder que se analiza aquí . Te permite escribir código como el siguiente:

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

Tenga en cuenta que solo tengo esto para trabajar con Linq 2 SQL. EntityFramework no implementa Expression.Invoke, que es necesario para que este método funcione. Tengo una pregunta sobre este tema aquí .

Brad Leach
fuente
Este es un gran método para aquellos que usan una capa de lógica empresarial en la parte superior de su repositorio junto con una herramienta como AutoMapper para mapear entre objetos de transferencia de datos y modelos de entidad. El uso del generador de predicados le permitirá modificar dinámicamente su IQueryable antes de enviarlo a AutoMapper para acoplarlo, es decir, llevar la lista a la memoria. Tenga en cuenta que también es compatible con Entity Framework.
chrisjsherm
3

Haciendo esto:

bool lastNameSearch = true/false; // depending if they want to search by last name,

teniendo esto en la wheredeclaración:

where (lastNameSearch && name.LastNameSearch == "smith")

significa que cuando se crea la consulta final, si lastNameSearches así, falsela consulta omitirá completamente cualquier SQL para la búsqueda de apellido.

James Livingston
fuente
Depende del proveedor de datos. LINQ-to-Entities no lo optimiza tan bien.
Suncat2000
1

No es lo más bonito, pero puede usar una expresión lambda y pasar sus condiciones opcionalmente. En TSQL, hago muchas de las siguientes acciones para que los parámetros sean opcionales:

DONDE Field = @FieldVar O @FieldVar ES NULO

Puede duplicar el mismo estilo con la siguiente lambda (un ejemplo de verificación de autenticación):

MyDataContext db = new MyDataContext ();

void RunQuery (cadena param1, cadena param2, int? param3) {

Func checkUser = usuario =>

((param1.Length> 0)? user.Param1 == param1: 1 == 1) &&

((param2.Length> 0)? user.Param2 == param2: 1 == 1) &&

((param3! = null)? user.Param3 == param3: 1 == 1);

Usuario encontradoUser = db.Users.SingleOrDefault (checkUser);

}

t3rse
fuente
1

Recientemente tuve un requisito similar y finalmente encontré esto en MSDN. Ejemplos de CSharp para Visual Studio 2008

Las clases incluidas en la muestra DynamicQuery de la descarga le permiten crear consultas dinámicas en tiempo de ejecución en el siguiente formato:

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

Con esto, puede crear una cadena de consulta dinámicamente en tiempo de ejecución y pasarla al método Where ():

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;
Andy Rose
fuente
1

Puede crear y utilizar este método de extensión

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}
Gustavo
fuente
0

Simplemente use el operador && de C #:

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

Editar: Ah, necesito leer más detenidamente. Quería saber cómo agregar condicionalmente cláusulas adicionales. En ese caso, no tengo ni idea. :) Lo que probablemente haría es preparar varias consultas y ejecutar la correcta, dependiendo de lo que termine necesitando.

El Pitufo
fuente
0

Podrías usar un método externo:

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Esto funcionaría, pero no se puede dividir en árboles de expresión, lo que significa que Linq to SQL ejecutaría el código de verificación en cada registro.

Alternativamente:

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

Eso podría funcionar en árboles de expresión, lo que significa que Linq to SQL estaría optimizado.

Keith
fuente
0

Bueno, lo que pensé fue que podrías poner las condiciones del filtro en una lista genérica de Predicados:

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

Eso da como resultado una lista que contiene "me", "meyou" y "mow".

Puede optimizar eso haciendo el foreach con los predicados en una función totalmente diferente que OR todos los predicados.

Jon Limjap
fuente