Linq para entidades - cláusula SQL "IN"


230

No T-SQL, você poderia ter uma consulta como:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

Como você replicaria isso em uma consulta LINQ to Entities? Isso é possível?

Respostas:


349

Você precisa invertê-lo em termos da maneira como pensa sobre isso. Em vez de "entrar" para encontrar os direitos de usuário do item atual em um conjunto predefinido de direitos de usuário aplicáveis, você está solicitando a um conjunto predefinido de direitos de usuário se ele contém o valor aplicável do item atual. É exatamente da mesma maneira que você encontraria um item em uma lista regular no .NET.

Há duas maneiras de fazer isso usando o LINQ, um usa a sintaxe da consulta e o outro usa a sintaxe do método. Essencialmente, eles são iguais e podem ser usados ​​alternadamente, dependendo da sua preferência:

Sintaxe da consulta:

var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

Sintaxe do método:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

Minha preferência pessoal nesse caso pode ser a sintaxe do método, porque, em vez de atribuir a variável, eu poderia fazer o foreach em uma chamada anônima como esta:

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

Sintaticamente, isso parece mais complexo, e você precisa entender o conceito de expressões ou delegados lambda para realmente descobrir o que está acontecendo, mas, como você pode ver, isso condensa o código em uma quantidade razoável.

Tudo se resume ao seu estilo e preferência de codificação - todos os três exemplos fazem a mesma coisa de maneira um pouco diferente.

Uma maneira alternativa nem usa o LINQ, você pode usar a mesma sintaxe de método substituindo "where" por "FindAll" e obter o mesmo resultado, que também funcionará no .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

Talvez eu tenha sido rápido em marcar como resposta, mas não recebo um.
9339 StevenMcD

1
fiel ao meu nome "FailBoy", descobri: o PI inseriu uma string [] e depois a utilizou e funcionou. Obrigado!
9339 StevenMcD

desculpe, esqueci de atualizar a matriz anônima;) Corrigi meu exemplo de código. Ainda bem que você descobriu por conta própria.
12139 BenAlabaster

28
Esta resposta estaria correta se a pergunta fosse sobre o Linq-to-SQL ou o Linq em geral. No entanto, como diz especificamente "Linq-to-Entities", esta resposta está incorreta. array.Contains (ainda) não é suportado pelo Linq-to-Entities.
29510

6
@ KristoferA - isso pode ter sido verdade para versões anteriores do EF, mas parece bom para mim com o EF4.
Drew Noakes

21

Isso deve bastar para o seu propósito. Ele compara duas coleções e verifica se uma coleção tem os valores correspondentes aos da outra coleção

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))


8

Eu irei para o Inner Join neste contexto. Se eu tivesse usado contém, ele iria repetir 6 vezes, apesar do fato de haver apenas uma correspondência.

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Desvantagens de Contém

Suponha que eu tenha dois objetos de lista.

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

Usando Contém, ele procurará cada item da Lista 1 na Lista 2, o que significa que a iteração ocorrerá 49 vezes !!!


5
Isso ignora completamente o fato de que a instrução é convertida em SQL. Veja aqui .
Gert Arnold

5

Essa pode ser a maneira possível pela qual você pode usar diretamente os métodos de extensão LINQ para verificar a cláusula

var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();

2

Também tentei trabalhar com algo semelhante ao SQL-IN - consultando um modelo de dados de entidade . Minha abordagem é um construtor de strings para compor uma grande expressão OR. Isso é terrivelmente feio, mas receio que seja o único caminho a seguir agora.

Agora, bem, isso é assim:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

Trabalhando com GUIDs neste contexto : Como você pode ver acima, sempre há a palavra "GUID" antes do GUID se nos fragmentos de string de consulta. Se você não adicionar isso, ObjectQuery<T>.Wherelança a seguinte exceção:

Os tipos de argumento 'Edm.Guid' e 'Edm.String' são incompatíveis para esta operação., Quase igual à expressão, linha 6, coluna 14.

Encontrou isso nos fóruns do MSDN, pode ser útil ter em mente.

Matthias

... ansioso pela próxima versão do .NET e Entity Framework, quando tudo melhorar. :)


2

Um método alternativo para a resposta do BenAlabaster

Primeiro de tudo, você pode reescrever a consulta assim:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

Certamente isso é mais 'prolixo' e uma dor de escrever, mas funciona da mesma forma.

Portanto, se tivéssemos algum método utilitário que facilitasse a criação desse tipo de expressão LINQ, estaríamos nos negócios.

com um método utilitário no lugar, você pode escrever algo como isto:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

Isso cria uma expressão que tem o mesmo efeito que:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

Mas o que é mais importante, na verdade, funciona no .NET 3.5 SP1.

Aqui está a função de encanamento que torna isso possível:

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector, 
        IEnumerable<TValue> values
    )
{     
    if (null == valueSelector) 
        throw new ArgumentNullException("valueSelector");

    if (null == values)
        throw new ArgumentNullException("values");  

    ParameterExpression p = valueSelector.Parameters.Single();

    if (!values.Any())   
        return e => false;

    var equals = values.Select(value =>
        (Expression)Expression.Equal(
             valueSelector.Body,
             Expression.Constant(
                 value,
                 typeof(TValue)
             )
        )
    );
   var body = equals.Aggregate<Expression>(
            (accumulate, equal) => Expression.Or(accumulate, equal)
    ); 

   return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Não vou tentar explicar esse método, exceto dizer que ele basicamente constrói uma expressão de predicado para todos os valores usando o valueSelector (ou seja, p => p.User_Rights) e ORs esses predicados juntos para criar uma expressão para o completo predicado

Fonte: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx


0

Exemplo real:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;

-14

Seriamente? Vocês nunca usaram

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)

9
-1 Tente fazer isso com 20 ou mais valores em uma tabela com mais de 1000 linhas e você verá rapidamente a vantagem da solução aceita. Além disso, não é fácil adicionar um número arbitrário de condições à instrução where (como se o usuário estivesse optando por incluir as opções 1 e 2, mas não 3).
Trisped

Bem, eu não precisava de nada do cientista maluco e essa resposta foi meu voto porque eu precisava de um AND e 2 ORS var SamplePoints = (de c em _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) em que c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) selecione c) .ToList () ;
precisa

@Trisped - o número de linhas (1000) não muda nada - ou estou faltando alguma coisa?
tymtam

@Tymski Sim, o número de linhas é importante. Quanto mais linhas, mais cálculos. Mesmo com o número de possíveis valores: Checks = NumValues * NumRows. Como esse é um cálculo do tipo M * N, se um for pequeno, o tempo para executar cada verificação necessária também será pequeno. Eu adicionei a restrição para que o cjm30305 soubesse como configurar um ambiente de teste onde mostrasse por que sua solução é ruim.
Trisped

@Trisped Você está dizendo que where new[] { 1, 2, 3 }.Contains(x)faz menos comparações, então where (x == 1 || x == 2 || x == 3)?
tymtam
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.