Selecione vários registros com base na lista de IDs com linq

122

Eu tenho uma lista contendo os IDs da minha UserProfiletabela. Como posso selecionar tudo com UserProfilesbase na lista de IDs que obtive em um varuso LINQ?

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(......);

Fiquei preso aqui. Eu posso fazer isso usando loops, etc. Mas eu prefiro fazer isso LINQ.

Yustme
fonte
4
pesquisar e encontrar são duas coisas diferentes. Mas como você pode olhar por cima do meu ombro pela Internet, você poderia me dizer como sabe que eu não procurei? espere, não conte! Você viu certo? meu ponto, exatamente.
Yustme
5
fazer uma pergunta custa mais tempo do que fazer uma pesquisa. da próxima vez, apenas assuma que 'ele / ela' fez uma pesquisa ou 10.
Yustme 30/05
2
Isso ainda chama bastante atenção, então pensei em mencionar que o ReSharper faz um ótimo trabalho ao sugerir locais onde você pode transformar código iterativo em instruções LINQ. Para pessoas novas no LINQ, pode ser uma ferramenta indispensável para esse fim.
Yuck

Respostas:

205

Você pode usar Contains()para isso. Isso parecerá um pouco atrasado quando você estiver realmente tentando produzir uma INcláusula, mas isso deve ser feito:

var userProfiles = _dataContext.UserProfile
                               .Where(t => idList.Contains(t.Id));

Também estou assumindo que cada UserProfileregistro terá um int Idcampo. Se não for esse o caso, você precisará ajustar em conformidade.

que nojo
fonte
Olá, sim, os registros de perfil de usuário contêm IDs. Então, de alguma forma, eu estaria fazendo algo como t => t.id == idList.Contains (id)?
Yustme 29/05
Contains()lidará com essa verificação de igualdade em cada idvalor, se você usá-lo como escrevi na resposta. Você não precisa escrever explicitamente em ==nenhum lugar ao tentar comparar os itens de um conjunto (a matriz) com outro (a tabela do banco de dados).
Yuck
Bem, o problema é que t contém todo o objeto UserProfile e o idList contém apenas int. O compilador reclamou de algo, mas eu consegui corrigi-lo. Obrigado.
Yustme
1
@Yuck - Não funciona para mim, diz que a função expirou! Desativou o carregamento lento, mas ainda falha.
Bhuvin 18/07/2014
1
Recebo "Não é possível converter a expressão lambda para o tipo 'int' porque não é um tipo de delegado". Como consertar isso?
Stian
90

A solução com .Where e .Contains possui complexidade de O (N quadrado). Simples. A junção deve ter um desempenho muito melhor (próximo a O (N) devido ao hash). Portanto, o código correto é:

_dataContext.UserProfile.Join(idList, up => up.ID, id => id, (up, id) => up);

E agora resultado da minha medição. Gerei 100 000 perfis de usuário e 100 000 IDs. A associação levou 32ms e .Aonde os .Contains levou 2 minutos e 19 segundos! Eu usei IEnumerable puro para este teste para provar minha afirmação. Se você usar Lista em vez de IEnumerable, .Where e .Contains serão mais rápidos. De qualquer forma, a diferença é significativa. O mais rápido. Onde. Contém é com Set <>. Tudo depende da complexidade das coletâneas subjacentes para .Contains. Veja este post para aprender sobre a complexidade do linq. Veja meu exemplo de teste abaixo:

    private static void Main(string[] args)
    {
        var userProfiles = GenerateUserProfiles();
        var idList = GenerateIds();
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        userProfiles.Join(idList, up => up.ID, id => id, (up, id) => up).ToArray();
        Console.WriteLine("Elapsed .Join time: {0}", stopWatch.Elapsed);
        stopWatch.Restart();
        userProfiles.Where(up => idList.Contains(up.ID)).ToArray();
        Console.WriteLine("Elapsed .Where .Contains time: {0}", stopWatch.Elapsed);
        Console.ReadLine();
    }

    private static IEnumerable<int> GenerateIds()
    {
       // var result = new List<int>();
        for (int i = 100000; i > 0; i--)
        {
            yield return i;
        }
    }

    private static IEnumerable<UserProfile> GenerateUserProfiles()
    {
        for (int i = 0; i < 100000; i++)
        {
            yield return new UserProfile {ID = i};
        }
    }

Saída do console:

Tempo decorrido: 00: 00: 00.0322546

Tempo decorrido. Onde. Contém tempo: 00: 02: 19.4072107

David Gregor
fonte
4
Você pode fazer backup disso com números?
Yustme
Bom, no entanto, me deixa curioso para saber quais seriam os horários quando Listusado. 1
Yustme 4/11/14
Ok, aqui estão os horários em que você está interessado: a lista levou 13,1 segundos e o HashSet levou 0,7 ms! Portanto, o .Where .Contains é melhor apenas no caso de HashSet (quando .Contains possui complexidade O (1)). Em outros casos, o .join é melhor
David Gregor
5
Eu recebo um Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.erro ao usar o datacontext LINQ2SQL.
Mayank Raichura
3
@ Yustme - o desempenho é sempre uma consideração. (Eu odeio ser o "esta deve ser a resposta aceita" cara, mas ...)
jleach
19

Ótimas respostas, mas não se esqueça de uma coisa IMPORTANTE - elas fornecem resultados diferentes!

  var idList = new int[1, 2, 2, 2, 2]; // same user is selected 4 times
  var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e)).ToList();

Isso retornará 2 linhas do banco de dados (e isso pode estar correto, se você quiser apenas uma lista classificada de usuários)

MAS, em muitos casos, você pode querer uma lista não classificada de resultados. Você sempre tem que pensar sobre isso como uma consulta SQL. Veja o exemplo no carrinho de compras da eshop para ilustrar o que está acontecendo:

  var priceListIDs = new int[1, 2, 2, 2, 2]; // user has bought 4 times item ID 2
  var shoppingCart = _dataContext.ShoppingCart
                     .Join(priceListIDs, sc => sc.PriceListID, pli => pli, (sc, pli) => sc)
                     .ToList();

Isso retornará 5 resultados do DB. Usar 'contém' estaria errado neste caso.

Tomino
fonte
13

Isso deve ser simples. Tente o seguinte:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Fabian Bigler
fonte