Wie würden Sie eine Nicht-In-Abfrage mit LINQ durchführen?


307

Ich habe zwei Sammlungen, die Eigentum Emailin beiden Sammlungen haben. Ich muss eine Liste der Elemente in der ersten Liste erhalten, Emaildie in der zweiten Liste nicht vorhanden sind. Mit SQL würde ich nur "nicht in" verwenden, aber ich kenne das Äquivalent in LINQ nicht. Wie geht das?

Bisher habe ich einen Join, wie ...

var matches = from item1 in list1
join item2 in list2 on item1.Email equals item2.Email
select new { Email = list1.Email };

Aber ich kann nicht beitreten, da ich den Unterschied brauche und der Beitritt fehlschlagen würde. Ich brauche eine Möglichkeit, Contains oder Exists zu verwenden, glaube ich. Ich habe nur noch kein Beispiel dafür gefunden.


3
Bitte beachten Sie, dass Echostorms Antwort Code erzeugt, der viel klarer zu lesen ist als Roberts
Nathan Koop

Antworten:


302

Ich weiß nicht, ob das dir helfen wird, aber ..

NorthwindDataContext dc = new NorthwindDataContext();    
dc.Log = Console.Out;

var query =    
    from c in dc.Customers    
    where !(from o in dc.Orders    
            select o.CustomerID)    
           .Contains(c.CustomerID)    
    select c;

foreach (var c in query) Console.WriteLine( c );

von der NOT IN-Klausel in LINQ zu SQL von Marco Russo


Aber ich benutze Linq für Entitäten, so dass ich bekomme "nur primitive Typen können Fehler verwendet werden". Gibt es irgendwelche Probleme ...? abgesehen von manuellem Iterieren und Finden der Liste.
Anfänger

13
Dies funktioniert gut für mich mit LINQ to Entities. Die SQL wird zu einer WHERE NOT EXISTS-Abfrage (Unterabfrage). Vielleicht gab es ein Update, das dies behebt?
Scottheckel

2
Ich denke, neuere Versionen von EF unterstützen. Enthält, und diese Frage markiert weder EF (Version) noch LinqToSQL. Daher muss die Frage und Antwort hier möglicherweise erweitert werden.
Brett Caswell

4
@ Robert Rouse - Der Link zu The Not in cluse in linq to sql funktioniert nicht mehr. Nur eine Info.
JonH

Der bereitgestellte Link führt zu einer Site, die als Malware enthaltend gekennzeichnet ist.
Mikeigsigs

334

Sie möchten den Operator Except.

var answer = list1.Except(list2);

Bessere Erklärung hier: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

HINWEIS: Diese Technik eignet sich am besten nur für primitive Typen, da Sie einen IEqualityComparer implementieren müssen , um die ExceptMethode mit komplexen Typen zu verwenden.


7
Verwenden von Except: Wenn Sie mit Listen komplexer Typen arbeiten, müssen Sie einen IEqualityComparer <MyComlplexType> implementieren, was es nicht so schön macht
Sakito

4
Sie nicht haben IEqualityComparer <T> zu implementieren , wenn Sie nur Referenz Gleichheit vergleichen wollen oder wenn Sie außer Kraft gesetzt T.Equals haben () und T.GetHashCode (). Wenn Sie IEqualityComparer <T> nicht implementieren, wird EqualityComparer <T> .Default verwendet.
Piedar

2
@Echostorm (und andere, die lesen): Wenn Sie ein Objekt "Anonym auswählen" ausführen, wird der HashCode durch die Eigenschaftswerte bestimmt. list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));Dies ist besonders nützlich, wenn Sie die Gleichheit bestimmen, indem Sie nur eine Reihe von Werten des komplexen Typs auswerten.
Brett Caswell

3
Tatsächlich hat jemand unten darauf hingewiesen, und ich denke richtig, dass es nicht notwendig wäre, IEquatityComparor<T,T>Objektvergleichsmethoden in einem LinqToSqlSzenario zu implementieren oder zu überschreiben . denn die Abfrage wird als / kompiliert zu / ausgedrückt als SQL dargestellt; Somit werden die Werte überprüft, nicht die Objektreferenz.
Brett Caswell

2
Mit dem konnte exceptich eine LINQ-Abfrage von 8-10 Sekunden auf eine halbe Sekunde
beschleunigen

61

Für Leute, die mit einer Gruppe von In-Memory-Objekten beginnen und eine Datenbank abfragen, ist dies der beste Weg:

var itemIds = inMemoryList.Select(x => x.Id).ToArray();
var otherObjects = context.ItemList.Where(x => !itemIds.Contains(x.Id));

Dies erzeugt eine nette WHERE ... IN (...)Klausel in SQL.


1
Eigentlich können Sie das in 3.5
George Silva

59

Elemente in der ersten Liste, bei denen die E-Mail in der zweiten Liste nicht vorhanden ist.

from item1 in List1
where !(list2.Any(item2 => item2.Email == item1.Email))
select item1;

16

Sie können eine Kombination aus Wo und Beliebig verwenden, um nicht in:

var NotInRecord =list1.Where(p => !list2.Any(p2 => p2.Email  == p.Email));

8

Sie können beide Sammlungen in zwei verschiedenen Listen aufnehmen, z. B. Liste1 und Liste2.

Dann schreibe einfach

list1.RemoveAll(Item => list2.Contains(Item));

Das wird funktionieren.


3
Schön, hat aber den Nebeneffekt, Elemente aus der Liste zu entfernen.
Tarik

7

In dem Fall, in dem man die verwendet Wenn Sie ADO.NET Entity Framework verwenden , funktioniert die EchoStorm-Lösung ebenfalls einwandfrei. Aber ich brauchte ein paar Minuten, um meinen Kopf darum zu wickeln. Angenommen, Sie haben einen Datenbankkontext, dc, und möchten Zeilen in Tabelle x finden, die nicht in Tabelle y verknüpft sind, sieht die vollständige Antwort wie folgt aus:

var linked =
  from x in dc.X
  from y in dc.Y
  where x.MyProperty == y.MyProperty
  select x;
var notLinked =
  dc.X.Except(linked);

Als Antwort auf Andys Kommentar kann man in einer LINQ-Abfrage zwei von haben. Hier ist ein vollständiges Arbeitsbeispiel mit Listen. Jede Klasse, Foo und Bar, hat eine ID. Foo hat einen "Fremdschlüssel" -Referenz auf Bar über Foo.BarId. Das Programm wählt alle Foo's aus, die nicht mit einer entsprechenden Leiste verknüpft sind.

class Program
{
    static void Main(string[] args)
    {
        // Creates some foos
        List<Foo> fooList = new List<Foo>();
        fooList.Add(new Foo { Id = 1, BarId = 11 });
        fooList.Add(new Foo { Id = 2, BarId = 12 });
        fooList.Add(new Foo { Id = 3, BarId = 13 });
        fooList.Add(new Foo { Id = 4, BarId = 14 });
        fooList.Add(new Foo { Id = 5, BarId = -1 });
        fooList.Add(new Foo { Id = 6, BarId = -1 });
        fooList.Add(new Foo { Id = 7, BarId = -1 });

        // Create some bars
        List<Bar> barList = new List<Bar>();
        barList.Add(new Bar { Id = 11 });
        barList.Add(new Bar { Id = 12 });
        barList.Add(new Bar { Id = 13 });
        barList.Add(new Bar { Id = 14 });
        barList.Add(new Bar { Id = 15 });
        barList.Add(new Bar { Id = 16 });
        barList.Add(new Bar { Id = 17 });

        var linked = from foo in fooList
                     from bar in barList
                     where foo.BarId == bar.Id
                     select foo;
        var notLinked = fooList.Except(linked);
        foreach (Foo item in notLinked)
        {
            Console.WriteLine(
                String.Format(
                "Foo.Id: {0} | Bar.Id: {1}",
                item.Id, item.BarId));
        }
        Console.WriteLine("Any key to continue...");
        Console.ReadKey();
    }
}

class Foo
{
    public int Id { get; set; }
    public int BarId { get; set; }
}

class Bar
{
    public int Id { get; set; }
}

arbeiten zwei froms in LINQ? das wäre hilfreich.
Andy

Andy: Ja, siehe überarbeitete Antwort oben.
Brett

4
var secondEmails = (from item in list2
                    select new { Email = item.Email }
                   ).ToList();

var matches = from item in list1
              where !secondEmails.Contains(item.Email)
              select new {Email = item.Email};

4

Man könnte es auch gebrauchen All()

var notInList = list1.Where(p => list2.All(p2 => p2.Email != p.Email));

2

Während dies ExceptTeil der Antwort ist, ist es nicht die ganze Antwort. Standardmäßig führt Except(wie einige der LINQ-Operatoren) einen Referenzvergleich für Referenztypen durch. Um nach Werten in den Objekten zu vergleichen, müssen Sie

  • implementieren IEquatable<T>in Ihrem Typ , oder
  • überschreiben EqualsundGetHashCode in Ihrem Typ, oder
  • Übergeben Sie eine Instanz eines Typs, der IEqualityComparer<T>für Ihren Typ implementiert ist

2
... wenn es sich um LINQ to Objects handelt. Wenn es sich um LINQ to SQL handelte, wird die Abfrage in SQL-Anweisungen übersetzt, die in der Datenbank ausgeführt werden. Dies gilt also nicht.
Lucas

1

Beispiel zur Vereinfachung der Liste von int.

List<int> list1 = new List<int>();
// fill data
List<int> list2 = new List<int>();
// fill data

var results = from i in list1
              where !list2.Contains(i)
              select i;

foreach (var result in results)
    Console.WriteLine(result.ToString());

1

INLaden Sie dieses Paket herunter, wenn Sie auch einen SQL-ähnlichen Operator in C # verwenden möchten:

Mshwf.NiceLinq

Es hat Inund NotInMethoden:

var result = list1.In(x => x.Email, list2.Select(z => z.Email));

Auch Sie können es auf diese Weise verwenden

var result = list1.In(x => x.Email, "a@b.com", "b@c.com", "c@d.com");

0

Danke, Brett. Ihr Vorschlag hat mir auch geholfen. Ich hatte eine Liste von Objekten und wollte diese anhand einer anderen Liste von Objekten filtern. Danke noch einmal....

Wenn jemand etwas braucht, schauen Sie sich bitte mein Codebeispiel an:

'First, get all the items present in the local branch database
Dim _AllItems As List(Of LocalItem) = getAllItemsAtBranch(BranchId, RecordState.All)

'Then get the Item Mappings Present for the branch
Dim _adpt As New gItem_BranchesTableAdapter
Dim dt As New ds_CA_HO.gItem_BranchesDataTable
    _adpt.FillBranchMappings(dt, BranchId)

Dim _MappedItems As List(Of LocalItem) = (From _item As LocalItem In _AllItems Join _
    dr As ds_CA_HO.gItem_BranchesRow In dt _
    On _item.Id Equals dr.numItemID _
    Select _item).ToList

_AllItems = _AllItems.Except(_MappedItems.AsEnumerable).ToList

 Return _AllItems

0

Ich habe dies nicht mit LINQ to Entities getestet :

NorthwindDataContext dc = new NorthwindDataContext();    
dc.Log = Console.Out;

var query =    
    from c in dc.Customers 
    where !dc.Orders.Any(o => o.CustomerID == c.CustomerID)   
    select c;

Alternative:

NorthwindDataContext dc = new NorthwindDataContext();    
dc.Log = Console.Out;

var query =    
    from c in dc.Customers 
    where dc.Orders.All(o => o.CustomerID != c.CustomerID)   
    select c;

foreach (var c in query) 
    Console.WriteLine( c );

0

Könnten Sie keine äußere Verknüpfung durchführen und nur die Elemente aus der ersten Liste auswählen, wenn die Gruppe leer ist? Etwas wie:

Dim result = (From a In list1
              Group Join b In list2 
                  On a.Value Equals b.Value 
                  Into grp = Group
              Where Not grp.Any
              Select a)

Ich bin mir nicht sicher, ob dies mit dem Entity-Framework auf irgendeine effiziente Weise funktionieren würde.


0

Alternativ können Sie Folgendes tun:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.