Linq: adicionar condições à cláusula where condicionalmente

103

Eu tenho uma consulta como esta

(from u in DataContext.Users
       where u.Division == strUserDiv 
       && u.Age > 18
       && u.Height > strHeightinFeet  
       select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();

Quero adicionar as várias condições, como idade, altura com base no fato de essas condições terem sido fornecidas ao método que executa esta consulta. Todas as condições incluirão a Divisão do usuário. Se a idade foi fornecida, desejo adicioná-la à consulta. Da mesma forma, se a altura for fornecida, quero adicioná-la também.

Se isso fosse feito usando consultas sql, eu teria usado o construtor de strings para anexá-las à consulta strSQL principal. Mas aqui no Linq eu só consigo pensar em usar uma condição IF onde escreverei a mesma consulta três vezes, com cada bloco IF tendo uma condição adicional. Existe uma maneira melhor de fazer isso?

user20358
fonte

Respostas:

182

Se você não chamar ToList()e seu mapeamento final para o tipo de DTO, você pode adicionar Wherecláusulas conforme avança e construir os resultados no final:

var query = from u in DataContext.Users
   where u.Division == strUserDiv 
   && u.Age > 18
   && u.Height > strHeightinFeet
   select u;

if (useAge)
   query = query.Where(u => u.Age > age);

if (useHeight)
   query = query.Where(u => u.Height > strHeightinFeet);

// Build the results at the end
var results = query.Select(u => new DTO_UserMaster
   {
     Prop1 = u.Name,
   }).ToList();

Isso ainda resultará apenas em uma única chamada para o banco de dados, que será efetivamente tão eficiente quanto escrever a consulta em uma passagem.

Reed Copsey
fonte
1
Preciso colocar todas as condições where na instrução "var query = .."?
user20358
4
As condições subsequentes de Onde são agregadas como OU ou como E?
Vi100
4
@ vi100 serão filtros adicionais, então AND
Reed Copsey
Graças a Deus pela simplicidade! Estou farto de ver mais de 20 consultas de Linq em linha quando o
texto
Por que estou 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
19

Eu geralmente uso o encadeamento de métodos, mas tenho o mesmo problema. E aqui está a extensão que uso

public static IQueryable<T> ConditionalWhere<T>(
        this IQueryable<T> source, 
        Func<bool> condition,
        Expression<Func<T, bool>> predicate)
    {
        if (condition())
        {
            return source.Where(predicate);
        }

        return source;
    }

Ajuda a evitar quebras de corrente. Também são iguais ConditionalOrderBye ConditionalOrderByDescendingúteis.

Yuriy Granovskiy
fonte
Útil, mas você poderia adicionar um exemplo de como ficaria em uso.
Suncat2000 de
1
Deveria ser assim: var fruits = await db.Fruits .ConditionalWhere (() => color! = Null, f => f.Color == color) .ConditionalWhere (() => maduro! = Null, f => f .Ripe == maduro) .ToListAsync ();
Yuriy Granovskiy
4
Funciona bem! Obrigado! Também criei uma sobrecarga para a condição como um valor booleano simples em vez de uma função, para torná-lo mais intuitivo, onde um delegado adicionaria complexidade desnecessária. Eu uso esse método de extensão com bastante frequência agora e aprecio muito sua solução.
Suncat2000 de
17

uma opção.

bool? age = null

(from u in DataContext.Users
           where u.Division == strUserDiv 
           && (age == null || (age != null && u.Age > age.Value))
           && u.Height > strHeightinFeet  
           select new DTO_UserMaster
           {
             Prop1 = u.Name,
           }).ToList();

ou você pode mudar para a sintaxe do método para linq e usar as condições if para anexar expressões à cláusula where.

Matthew Vines
fonte
3

Simplesmente estou usando na minha cláusula where como

    public IList<ent_para> getList(ent_para para){
     db.table1.Where(w=>(para.abc!=""?w.para==para.abc:true==true) && (para.xyz!=""?w.xyz==para.xyz:true==true)).ToList();
}
Próprio
fonte
3

Com base em certas condições, adicione a condição where ...

from u in DataContext.Users
where u.Division == strUserDiv 
&& u.Age != null ? u.Age > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
 select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();
Melu
fonte
2

Aqui está meu código para fazer uma coisa semelhante. Este é um método em minha API WCF SOAP Web Service.

    public FruitListResponse GetFruits(string color, bool? ripe)
    {
        try
        {
            FruitContext db = new FruitContext();
            var query = db.Fruits.Select(f => f);
            if (color != null)
            {
                query = query.Where(f => f.Color == color);
            }
            if (ripe != null)
            {
                query = query.Where(f => f.Ripe == ripe);
            }
            return new FruitListResponse
            {
                Result = query.Select(f => new Fruit { Id = f.FruitId, Name = f.Name }).ToList()
            };
        }
        catch (Exception e)
        {
            return new FruitListResponse { ErrorMessage = e.Message };
        }
    }

A consulta de base é o Select(f => f)que significa basicamente TUDO, e as Wherecláusulas são opcionalmente anexadas a ela. O final Selecté opcional. Eu uso para converter os objetos de linhas de banco de dados em objetos de resultado "Fruta".

John Henckel
fonte
0

Assumindo o seguinte parâmetro,

Int? Age = 18;

Basta usar &&e ||operadores condicionais podemos ter outra versão.

(from u in DataContext.Users
where u.Division == strUserDiv 
    && (Age == null || u.Age > Age)
    && (Param1 == null || u.param1 == Param1)
    && u.Height > strHeightinFeet
select new DTO_UserMaster
{
    Prop1 = u.Name,
}).ToList();

Como Param1, você pode adicionar qualquer número de parâmetros para a condição de pesquisa.

Sushant Yelpale
fonte
0

Acabei de encontrar isso procurando por outra coisa, mas pensei em jogar na versão lambda.

Primeiro, eu criaria uma classe como esta para passar parâmetros para uma camada de dados:

   public class SearchParameters() {
       public int? Age {get; set;}
       public string Division {get;set;}
       etc
    }

Então, em minha camada de dados, algo assim:

public IQueryable<User> SearchUsers(SearchParameters params) 
{
    var query = Context.Users;
    if (params.Age.HasValue)
    {
         query = query.Where(u => u.Age == params.Age.Value);
    }
    if (!string.IsNullOrEmpty(params.Division)
    {
        query = query.Where(u => u.Division == params.Division);
    }
    etc
    return query;
}

Onde você materializa a consulta é com você. Pode haver uma camada entre o aplicativo e os dados que converte representações específicas do banco de dados em agnósticas do banco de dados (talvez você consulte várias fontes de dados). Essa camada pode obter vários tipos de consultáveis ​​dessas fontes e mapeá-los para uma representação POCO comum, por exemplo.

Scott Peterson
fonte
Ops, não vi a resposta de John Henckel. Mesma ideia.
Scott Peterson
0

Apenas para adicionar a resposta aceita acima aqui , se você estiver fazendo uma pesquisa dinâmica em uma junção, considere retornar um novo objeto com ambas as tabelas (t1, t2) na consulta inicial do linq para que você possa acessá-los individualmente para fazer o condicional procurar.

var query = from t1 in _context.Table1
            join t2 in _context.Table2 on t1.Table1Id equals t2.Table1IdId
            select new { t1, t2 };

        if (!string.IsNullOrEmpty(searchProperty1))
        {
            query = query.Where(collection => collection.t1.TableColumn == searchProperty1);
        }
        if (!string.IsNullOrEmpty(searchProperty2))
        {
            query = query.Where(collection => collection.t2.TableColumn == searchProperty2);
        }
        ....etc.

Eu obtive a resposta que estava procurando aqui com relação a unir duas tabelas e consultar colunas específicas em qualquer uma das tabelas

Riaan Saayman
fonte