Pergunta

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 algo à string de consulta, mas quero fazer isso com o Linq.Como posso adicionar condicionalmente cláusulas where?

Foi útil?

Solução

se você quiser filtrar apenas se determinados critérios forem aprovados, 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ões seja exatamente o que você deseja.Dessa forma o SQL criado será exatamente o que você precisa e nada menos.

Outras dicas

Se você precisar filtrar com base em uma Lista/Array 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();

    }

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 acessar o banco de dados.O comando não será executado até .ToList() no final.

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 recebe o 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;
}

Outra opção seria usar algo como o PredicateBuilder discutido aqui.Ele permite que você escreva 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 só consegui que isso funcionasse com Linq 2 SQL.EntityFramework não implementa Expression.Invoke, que é necessário para que este método funcione.Eu tenho uma pergunta sobre esse assunto aqui.

Fazendo isso:

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

tendo isso no where declaração:

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

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

Resolvi isso com um método de extensão para permitir que o LINQ fosse habilitado condicionalmente no meio de uma expressão fluente.Isso elimina a necessidade de quebrar a expressão com if declaraçõ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 ? source : branch(source);
    }

Isso permite que você faça isso:

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

Aqui também está um 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 ? source : branch(source);
    }

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

ONDE Campo = @FieldVar OU @FieldVar É NULO

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?parâmetro3){

Func checkUser = usuário =>

((param1.Comprimento > 0)?usuário.Param1 == parâmetro1:1 == 1) &&

((param2.Comprimento > 0)?usuário.Param2 == param2:1 == 1) &&

((param3! = nulo)?usuário.Param3 == parâmetro3:1 == 1);

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

}

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

Basta usar o operador && do C#:

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

Editar:Ah, preciso ler com mais atenção.Você queria saber como condicionalmente adicione cláusulas adicionais.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 precisasse.

Você poderia 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 o Linq to SQL seria otimizado.

Bem, o que pensei foi 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 OR todos os predicados.

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;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top