Consultas Linq Condicionais
-
08-06-2019 - |
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?
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;
}