Seleziona più record in base all'elenco di ID con linq


122

Ho un elenco che contiene gli ID della mia UserProfiletabella. Come posso selezionare tutto in UserProfilesbase all'elenco di ID che ho ottenuto in un varutilizzo LINQ?

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

Sono rimasto bloccato proprio qui. Posso farlo usando i cicli for ecc. Ma preferisco farlo con LINQ.


4
cercare e trovare sono 2 cose diverse. Ma dal momento che puoi guardarmi alle spalle attraverso Internet, potresti dirmi come fai a sapere che non ho cercato? aspetta non dirlo! Hai visto bene? esattamente il mio punto.
Yustme

5
fare una domanda costa più tempo che fare una ricerca. la prossima volta supponi che "lui / lei" abbia eseguito una ricerca o 10.
Yustme

2
Questo riceve ancora un po 'di attenzione, quindi ho pensato di menzionare che ReSharper fa un ottimo lavoro nel suggerire luoghi in cui è possibile trasformare il codice iterativo in istruzioni LINQ. Per le persone che non conoscono LINQ può essere uno strumento indispensabile da avere solo a questo scopo.
Yuck

Risposte:


206

Puoi usare Contains()per quello. Ti sembrerà un po 'indietro quando stai davvero cercando di produrre una INclausola, ma questo dovrebbe farlo:

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

Suppongo anche che ogni UserProfilerecord avrà un int Idcampo. Se non è così, dovrai adattarti di conseguenza.


Ciao, sì, i record del profilo utente contengono gli ID. Quindi in qualche modo farei qualcosa come t => t.id == idList.Contains (id)?
Yustme

Contains()gestirà quel controllo di uguaglianza su ogni idvalore se lo usi come ho scritto nella risposta. Non è necessario scrivere esplicitamente da ==nessuna parte quando si tenta di confrontare gli elementi di un insieme (l'array) con un altro (la tabella del database).
Yuck

Ebbene, il problema è che t contiene l'intero oggetto di UserProfile e l'idList contiene solo gli int. Il compilatore si è lamentato di qualcosa ma sono riuscito a risolverlo. Grazie.
Yustme

2
@Yuck - Non funziona per me, dice che la funzione è scaduta! Ho disabilitato il caricamento lento ma non riesce ancora.
bhuvin

1
Ottengo "Impossibile convertire l'espressione lambda nel tipo" int "perché non è un tipo delegato". Come risolverlo?
Stian

92

La soluzione con .Where e .Contains ha una complessità di O (N quadrato). Semplice .Join dovrebbe avere prestazioni molto migliori (vicino a O (N) a causa dell'hashing). Quindi il codice corretto è:

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

E ora il risultato della mia misurazione. Ho generato 100.000 UserProfiles e 100.000 ID. L'iscrizione ha richiesto 32 ms e .Where con .Contains ha impiegato 2 minuti e 19 secondi! Ho usato puro IEnumerable per questo test per dimostrare la mia affermazione. Se usi List invece di IEnumerable, .Where e .Contains saranno più veloci. Comunque la differenza è significativa. Il .Where .Contains più veloce è con Set <>. Tutto dipende dalla complessità delle raccolte sottostanti per .Contains. Guarda questo post per conoscere la complessità di linq. Guarda il mio esempio di prova qui sotto:

    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};
        }
    }

Uscita console:

Tempo di iscrizione trascorso: 00: 00: 00.0322546

Trascorso .Dove .Contiene il tempo: 00: 02: 19.4072107


4
Puoi confermarlo con i numeri?
Yustme

Bello, tuttavia mi incuriosisce quali sarebbero i tempi quando Listviene utilizzato. +1
Yustme

Ok, ecco i tempi che ti interessano: List ha impiegato 13,1 secondi e HashSet ha impiegato 0,7 ms! Quindi .Where .Contains è migliore solo in caso di HashSet (quando .Contains ha complessità O (1)). In altri casi, il .Join è migliore
David Gregor,

5
Ricevo un Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.errore quando utilizzo il contesto dati LINQ2SQL.
Mayank Raichura

3
@Yustme - le prestazioni sono sempre una considerazione. (Odio essere il tipo "questa dovrebbe essere la risposta accettata", ma ...)
jleach

19

Belle risposte a proposito, ma non dimenticare una cosa IMPORTANTE : forniscono risultati diversi!

  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();

Questo restituirà 2 righe dal DB (e questo potrebbe essere corretto, se vuoi solo un elenco ordinato distinto di utenti)

MA in molti casi potresti volere un elenco di risultati non ordinato . Devi sempre pensarci come se fosse una query SQL. Si prega di vedere l'esempio con il carrello degli acquisti di eShop per illustrare cosa sta succedendo:

  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();

Ciò restituirà 5 risultati da DB. L'uso di "contiene" sarebbe sbagliato in questo caso.


13

Dovrebbe essere semplice. Prova questo:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.