Consultas Linq Condicionais

92

Estamos trabalhando em um Log Viewer. O uso terá a opção de filtrar por usuário, gravidade, etc. Na época do Sql eu adicionaria à string de consulta, mas quero fazer isso com o Linq. Como posso adicionar cláusulas where condicionalmente?

sgwill
fonte

Respostas:

156

se você quiser filtrar apenas se certos critérios forem passados, faça algo assim

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

Fazer isso dessa forma permitirá que sua árvore de expressão seja exatamente o que você deseja. Dessa forma, o SQL criado será exatamente o que você precisa e nada menos.

Darren Kopp
fonte
2
Olá. Você tem alguma sugestão para fazer as cláusulas where ORs em vez de ANDs ..?
Jon H
1
Sim ... é um pouco difícil de fazer. O melhor que vi é através do padrão de especificação e puxando o predicado para dentro da especificação e, em seguida, chamando a especificação. Ou (someOtherSpecification). Basicamente, você deve escrever um pouco sua própria árvore de expressão. Exemplo de código e explicação aqui: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
Eu tenho uma pergunta estúpida, se esses logs estão sendo adquiridos do banco de dados, vamos obter todos os logs e, em seguida, filtrá-los na memória? Se sim, como posso passar as condições para o banco de dados
Ali Umair
não os está filtrando na memória. ele está construindo uma consulta e enviando todas as condições no banco de dados (pelo menos para a maioria dos provedores linq-to-x)
Darren Kopp
recebendo este erroLINQ 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

Se você precisar filtrar com base em uma lista / matriz, use o seguinte:

    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
fonte
3
Esta é de longe a melhor e mais correta resposta. O condicional || só compara a primeira parte e pula a segunda se a primeira parte for verdadeira ... muito bem feito!
Serj Sagan
1
Essa construção inclui a parte 'ou' da expressão na consulta SQL gerada. A resposta aceita gerará afirmações mais eficientes. Dependendo das otimizações do provedor de dados, é claro. LINQ-to-SQL pode ter uma otimização melhor, mas LINQ-to-Entities não.
Suncat2000 de
20

Terminei usando uma resposta semelhante à de Daren, mas com uma interface 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();

Isso cria a consulta antes de chegar ao banco de dados. O comando não será executado até .ToList () no final.

sgwill
fonte
14

Quando se trata de linq condicional, gosto muito do padrão de filtros e tubos.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Basicamente, você cria um método de extensão para cada caso de filtro que leva em IQueryable e um 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
fonte
8

Resolvi isso com um método de extensão para permitir que LINQ seja habilitado condicionalmente no meio de uma expressão fluente. Isso elimina a necessidade de dividir a expressão com ifinstruções.

.If() método de extensão:

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

Isso permite que você faça o seguinte:

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

Esta é também uma IEnumerable<T>versão que irá lidar com a maioria das outras expressões 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
fonte
4

Outra opção seria usar algo como o PredicateBuilder discutido aqui . Ele permite que você escreva um código como o seguinte:

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;

Observe que eu só tenho isso para funcionar com o Linq 2 SQL. EntityFramework não implementa Expression.Invoke, que é necessário para que este método funcione. Eu tenho uma pergunta sobre este assunto aqui .

Brad Leach
fonte
Este é um ótimo método para aqueles que usam uma Business Logic Layer no topo de seu repositório junto com uma ferramenta como o AutoMapper para mapear entre objetos de transferência de dados e modelos de entidade. Usar o construtor de predicado permitirá que você modifique dinamicamente seu IQueryable antes de enviá-lo ao AutoMapper para achatamento, ou seja, trazer a lista para a memória. Observe que ele também oferece suporte ao Entity Framework.
chrisjsherm de
3

Fazendo isso:

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

tendo isso na wheredeclaração:

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

significa que quando a consulta final é criada, se lastNameSearchfor, falsea consulta omitirá completamente qualquer SQL para a pesquisa de sobrenome.

James Livingston
fonte
Depende do provedor de dados. LINQ-to-Entities não o otimiza muito bem.
Suncat2000 de
1

Não é a coisa mais bonita, mas você pode usar uma expressão lambda e passar suas condições opcionalmente. No TSQL, faço muitas das seguintes ações para tornar os parâmetros opcionais:

WHERE Field = @FieldVar OU @FieldVar IS NULL

Você pode duplicar o mesmo estilo com o seguinte lambda (um exemplo de verificação de autenticação):

MyDataContext db = new MyDataContext ();

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

Func checkUser = user =>

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

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

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

Usuário foundUser = db.Users.SingleOrDefault (checkUser);

}

t3rse
fonte
1

Eu tive um requisito semelhante recentemente e finalmente encontrei isso no MSDN. Amostras CSharp para Visual Studio 2008

As classes incluídas na amostra DynamicQuery do download permitem que você crie consultas dinâmicas em tempo de execução no seguinte formato:

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

Usando isso, você pode construir uma string de consulta dinamicamente em tempo de execução e passá-la para o 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
fonte
1

Você pode criar e usar este método de extensão

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

Basta usar o operador && do C #:

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

Edit: Ah, preciso ler com mais atenção. Você queria saber como adicionar cláusulas adicionais condicionalmente . Nesse caso, não tenho ideia. :) O que eu provavelmente faria seria apenas preparar várias consultas e executar a correta, dependendo do que eu acabasse precisando.

O Smurf
fonte
0

Você pode usar um método externo:

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

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Isso funcionaria, mas não pode ser dividido em árvores de expressão, o que significa que o Linq to SQL executaria o código de verificação em cada registro.

Alternativamente:

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

Isso pode funcionar em árvores de expressão, o que significa que Linq para SQL seria otimizado.

Keith
fonte
0

Bem, pensei que você poderia colocar as condições do filtro em uma 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);               

Isso resulta em uma lista contendo "me", "meyou" e "mow".

Você poderia otimizar isso fazendo o foreach com os predicados em uma função totalmente diferente que executa o OR de todos os predicados.

Jon Limjap
fonte