Linq to Entities – cláusula SQL “IN”

Em 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? É mesmo possível?

Você precisa transformá-lo em sua cabeça em termos da maneira como você está pensando sobre isso. Em vez de fazer “in” para localizar os direitos de usuário do item atual em um conjunto predefinido de direitos de usuário aplicáveis, você está solicitando um conjunto predefinido de direitos de usuário se ele contiver o valor aplicável do item atual. Isso é exatamente da mesma maneira que você encontraria um item em uma lista regular no .NET.

Existem duas maneiras de fazer isso usando o LINQ, uma usa a syntax de consulta e a outra usa a syntax do método. Essencialmente, eles são os mesmos e podem ser usados ​​de forma intercambiável, dependendo da sua preferência:

Sintaxe de 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 nesta instância pode ser a syntax do método, porque em vez de atribuir a variável, eu poderia fazer o foreach por 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ê tem que entender o conceito de expressões lambda ou delegates para realmente descobrir o que está acontecendo, mas como você pode ver, isso condensa o código de forma justa.

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

Uma maneira alternativa nem sequer usa LINQ, você pode usar a mesma syntax 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; } 

Isso deve ser suficiente para o seu propósito. Ele compara duas collections e verifica se uma coleção possui os valores correspondentes àqueles da outra coleção.

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

Se você estiver usando o VS2008 / .net 3.5, consulte a dica nº 8 de Alex James: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-using-linq-to-entities.aspx

Caso contrário, basta usar o método array.Contains (someEntity.Member).

Eu irei para Inner Join neste contexto. Se eu tivesse usado contém, iteraria 6 vezes, apesar do fato de que há 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 objects de lista.

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

Usando Contains, ele procurará cada item da List 1 na List 2, o que significa que a iteração acontecerá 49 vezes !!!

Esta poderia ser a maneira possível em que você pode usar diretamente os methods de extensão LINQ para verificar a cláusula in

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

Eu também tentei trabalhar com uma coisa do tipo SQL-IN – consultando um Modelo de Dados de Entidade . Minha abordagem é um construtor de string para compor uma grande expressão OR. Isso é terrivelmente feio, mas temo que seja a única maneira de ir agora.

Agora bem, isso é assim:

 Queue productIds = new Queue(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, há sempre a palavra “GUID” antes do GUID ifself nos fragments de cadeia de caracteres de consulta. Se você não adicionar isso, ObjectQuery.Where lança a seguinte exceção:

Os tipos de argumento ‘Edm.Guid’ e ‘Edm.String’ são incompatíveis para esta operação., Próximo a expressão igual, linha 6, coluna 14.

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

Matias

… ansioso pela próxima versão do .NET e do Entity Framework, quando tudo ficar melhor. 🙂

Um método alternativo para a resposta da BenAlabaster

Primeiro de tudo, você pode rewrite 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 “expressivo” e doloroso de escrever, mas funciona da mesma forma.

Então, se tivéssemos algum método utilitário que facilitasse a criação desse tipo de expressões LINQ, estaríamos no negócio.

Com um método utilitário, você pode escrever algo assim:

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

Isto constrói 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 contra o .NET 3.5 SP1.

Aqui está a function de encanamento que torna isso possível:

 public static Expression> BuildOrExpression( Expression> valueSelector, IEnumerable 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( (accumulate, equal) => Expression.Or(accumulate, equal) ); return Expression.Lambda>(body, p); } 

Eu não vou tentar explicar este método, além de dizer que essencialmente constrói uma expressão de predicado para todos os valores usando o valorSelector (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

A sério? Vocês nunca usaram

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