Wybierz wiele rekordów na podstawie listy identyfikatorów za pomocą linq


122

Mam listę zawierającą identyfikatory mojego UserProfilestołu. Jak mogę wybrać wszystko UserProfilesna podstawie listy identyfikatorów, które mam w varużyciu LINQ?

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

Utknąłem tutaj. Mogę to zrobić używając pętli for itp. Ale wolałbym to zrobić z LINQ.


4
wyszukiwanie i znajdowanie to dwie różne rzeczy. Ale skoro możesz spojrzeć przez ramię przez internet, czy możesz mi powiedzieć, skąd wiesz, że nie szukałem? czekaj, nie mów! Widziałeś to dobrze? dokładnie o co mi chodzi.
Yustme

5
zadanie pytania kosztuje więcej czasu niż wyszukiwanie. następnym razem załóżmy, że „on / ona” przeprowadził wyszukiwanie lub 10.
Yustme

2
To wciąż przyciąga sporo uwagi, więc pomyślałem, że wspomnę, że ReSharper bardzo dobrze radzi sobie z sugerowaniem miejsc, w których można zamienić kod iteracyjny w instrukcje LINQ. Dla osób nowych w LINQ może to być niezbędne narzędzie do tego celu.
Fuj,

Odpowiedzi:


207

Możesz użyć Contains()do tego. Będzie to trochę cofnięte, gdy naprawdę próbujesz stworzyć INklauzulę, ale powinno to wystarczyć:

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

Zakładam też, że każdy UserProfilerekord będzie miał int Idpole. Jeśli tak nie jest, musisz odpowiednio dostosować.


Cześć, tak, rekordy profili użytkowników zawierają identyfikatory. Więc jakoś zrobiłbym coś takiego jak t => t.id == idList.Contains (id)?
Yustme

Contains()obsłuży to sprawdzenie równości dla każdej idwartości, jeśli użyjesz jej tak, jak napisałem w odpowiedzi. Nie musisz ==nigdzie jawnie pisać, gdy próbujesz porównać elementy jednego zestawu (tablicy) z innym (tabela bazy danych).
Fuj

Cóż, problem polega na tym, że t przechowuje cały obiekt UserProfile, a idList zawiera tylko int. Kompilator narzekał na coś, ale udało mi się to naprawić. Dzięki.
Yustme

2
@ Yuck - nie działa dla mnie, mówi, że upłynął limit czasu funkcji! Wyłączyłem leniwe ładowanie, ale nadal nie działa.
bhuvin

1
Otrzymuję komunikat „Nie można przekonwertować wyrażenia lambda na typ„ int ”, ponieważ nie jest to typ delegata”. Jak to naprawić?
Stian

92

Rozwiązanie z .Where i .Contains ma złożoność O (N kwadrat). Prosty .Join powinien mieć dużo lepszą wydajność (blisko O (N) ze względu na haszowanie). Więc poprawny kod to:

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

A teraz wynik mojego pomiaru. Wygenerowałem 100 000 profili użytkowników i 100 000 identyfikatorów. Dołączenie zajęło 32 ms, a gdzie z .Contains zajęło 2 minuty i 19 sekund! Użyłem czystego IEnumerable do tego testu, aby udowodnić moje stwierdzenie. Jeśli używasz listy zamiast IEnumerable, .Where i .Contains będą działać szybciej. Zresztą różnica jest znacząca. Najszybszy .Gdzie .Contains to Set <>. Wszystko zależy od złożoności bazowych kolekcji dla .Contains. Spójrz na ten post, aby dowiedzieć się o złożoności linq Spójrz na moją próbkę testową poniżej:

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

Wyjście konsoli:

Upłynęło. Czas dołączenia: 00: 00: 00,0322546

Upłynęło .Gdzie .Zawiera czas: 00: 02: 19.4072107


4
Czy możesz to poprzeć liczbami?
Yustme

Fajnie, jednak ciekawi mnie, jakie byłyby czasy, kiedy Listjest używany. +1
Yustme

Ok, oto czasy, którymi jesteś zainteresowany: Lista zajęła 13,1 sekundy, a HashSet 0,7 ms! Więc .Where .Contains jest najlepsze tylko w przypadku HashSet (gdy .Contains ma złożoność O (1)). W innych przypadkach .Join jest lepszy
David Gregor

5
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.Podczas korzystania z kontekstu danych LINQ2SQL pojawia się błąd.
Mayank Raichura

3
@Yustme - zawsze liczy się wydajność . (Nienawidzę być facetem "to powinna być akceptowana odpowiedź", ale ...)
jleach

19

Ładne odpowiedzi powyżej, ale nie zapominaj o jednej WAŻNE - dają różne wyniki!

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

Spowoduje to zwrócenie 2 wierszy z bazy danych (i może to być poprawne, jeśli chcesz mieć tylko odrębną posortowaną listę użytkowników)

ALE w wielu przypadkach możesz potrzebować niesortowanej listy wyników. Zawsze musisz myśleć o tym jak o zapytaniu SQL. Zobacz przykład z koszykiem sklepu internetowego, aby zilustrować, co się dzieje:

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

To zwróci 5 wyników z DB. W tym przypadku użycie atrybutu „zawiera” byłoby niewłaściwe.


13

To powinno być proste. Spróbuj tego:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.