Wählen Sie mehrere Datensätze basierend auf der Liste der IDs mit linq aus

122

Ich habe eine Liste mit IDs meiner UserProfileTabelle. Wie kann ich alle UserProfilesbasierend auf der Liste der IDs auswählen, die ich in einer varVerwendung erhalten habe LINQ?

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

Ich bin genau hier festgefahren. Ich kann dies mit for-Schleifen usw. tun LINQ. Aber ich würde dies lieber mit tun .

Yustme
quelle
4
Suchen und Finden sind zwei verschiedene Dinge. Aber da Sie über das Internet über meine Schulter schauen können, können Sie mir sagen, woher Sie wissen, dass ich nicht gesucht habe? Warte, sag es nicht! Du hast es richtig gesehen? Genau mein Punkt.
Yustme
5
Das Stellen einer Frage kostet mehr Zeit als das Suchen. Nächstes Mal einfach annehmen, dass 'er / sie' eine Suche durchgeführt hat oder 10.
Yustme
2
Dies wird immer noch beachtet, daher dachte ich, ich würde erwähnen, dass ReSharper sehr gute Arbeit leistet, um Orte vorzuschlagen, an denen Sie iterativen Code in LINQ-Anweisungen umwandeln können. Für LINQ-Neulinge kann es ein unverzichtbares Werkzeug sein, das nur für diesen Zweck zur Verfügung steht.
Yuck

Antworten:

205

Sie können dafür verwenden Contains(). Es wird sich ein wenig rückwärts anfühlen, wenn Sie wirklich versuchen, eine INKlausel zu erstellen , aber dies sollte es tun:

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

Ich gehe auch davon aus, dass jeder UserProfileDatensatz ein int IdFeld haben wird. Wenn dies nicht der Fall ist, müssen Sie entsprechend anpassen.

Yuck
quelle
Hallo, ja, die Benutzerprofildatensätze enthalten IDs. Also würde ich irgendwie so etwas wie t => t.id == idList.Contains (id) machen?
Yustme
Contains()wird diese Gleichheitsprüfung für jeden idWert durchführen, wenn Sie ihn so verwenden, wie ich es in der Antwort geschrieben habe. Sie müssen ==nirgendwo explizit schreiben , wenn Sie versuchen, die Elemente eines Satzes (des Arrays) mit einem anderen (der Datenbanktabelle) zu vergleichen.
Yuck
Das Problem ist, dass t das gesamte Objekt von UserProfile enthält und die idList nur Ints enthält. Der Compiler hat sich über etwas beschwert, aber ich habe es geschafft, es zu beheben. Vielen Dank.
Yustme
1
@ Yuck - Funktioniert nicht für mich, sagt Funktion abgelaufen! Haben Lazy Laden deaktiviert, schlägt aber immer noch fehl.
Bhuvin
1
Ich erhalte die Meldung "Lambda-Ausdruck kann nicht in Typ 'int' konvertiert werden, da es sich nicht um einen Delegatentyp handelt". Wie kann man das beheben?
Stian
90

Die Lösung mit .Where und .Contains hat eine Komplexität von O (N Quadrat). Simple .Join sollte eine viel bessere Leistung haben (nahe an O (N) aufgrund von Hashing). Der richtige Code lautet also:

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

Und jetzt Ergebnis meiner Messung. Ich habe 100 000 UserProfiles und 100 000 IDs generiert. Der Beitritt dauerte 32ms und .Wo mit .Contains dauerte 2 Minuten und 19 Sekunden! Ich habe für diese Tests reines IEnumerable verwendet, um meine Aussage zu beweisen. Wenn Sie List anstelle von IEnumerable verwenden, sind .Where und .Contains schneller. Auf jeden Fall ist der Unterschied signifikant. Die schnellste .Wo .Contains ist mit Set <>. Alles hängt von der Komplexität der zugrunde liegenden Coletions für .Contains ab. Schauen Sie sich diesen Beitrag an, um mehr über die Komplexität von Linq zu erfahren. Schauen Sie sich mein Testbeispiel unten an:

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

Konsolenausgabe:

Abgelaufen. Beitrittszeit: 00: 00: 00.0322546

Abgelaufen. Wo. Enthält Zeit: 00: 02: 19.4072107

David Gregor
quelle
4
Können Sie das mit Zahlen belegen?
Yustme
Schön, macht mich aber neugierig, wie die Timings aussehen würden, wenn sie Listverwendet werden. +1
Yustme
Ok, hier sind die Timings, die Sie interessieren: Die Liste dauerte 13,1 Sekunden und HashSet dauerte 0,7 ms! Daher ist .Where .Contains nur im Fall von HashSet am besten geeignet (wenn .Contains die Komplexität O (1) aufweist). In anderen Fällen ist die .Join besser
David Gregor
5
Ich erhalte eine Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.Fehlermeldung bei der Verwendung von LINQ2SQL-Datenkontext.
Mayank Raichura
3
@Yustme - Leistung ist immer eine Überlegung. (Ich hasse es, der Typ zu sein, "das sollte die akzeptierte Antwort sein", aber ...)
Jleach
19

Schöne Antworten oben, aber vergessen Sie nicht eine WICHTIGE Sache - sie liefern unterschiedliche Ergebnisse!

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

Dies gibt 2 Zeilen aus der Datenbank zurück (und dies könnte korrekt sein, wenn Sie nur eine eindeutige sortierte Liste von Benutzern wünschen).

ABER in vielen Fällen möchten Sie möglicherweise eine unsortierte Ergebnisliste. Sie müssen immer wie bei einer SQL-Abfrage darüber nachdenken. Das Beispiel mit dem Warenkorb von eshop zeigt, was los ist:

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

Dies gibt 5 Ergebnisse von DB zurück. Die Verwendung von 'enthält' wäre in diesem Fall falsch.

Tomino
quelle
13

Das sollte einfach sein. Versuche dies:

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