Jak zrobiłbyś zapytanie „nie w” z LINQ?


307

Mam dwie kolekcje, które mają właściwości Emailw obu kolekcjach. Muszę uzyskać listę elementów na pierwszej liście, których Emailnie ma na drugiej liście. W przypadku SQL użyłbym po prostu „nie w”, ale nie znam odpowiednika w LINQ. Jak to się robi?

Do tej pory mam połączenie, jak ...

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

Ale nie mogę dołączyć, ponieważ potrzebuję różnicy, a połączenie się nie powiedzie. Potrzebuję sposobu, w jaki wierzę, zawiera lub istnieje. Po prostu nie znalazłem jeszcze takiego przykładu.


3
Należy pamiętać, że odpowiedź Echostorma tworzy kod, który jest o wiele czytelniejszy do odczytania niż odpowiedź Roberta
Nathan Koop

Odpowiedzi:


302

Nie wiem czy to ci pomoże, ale ...

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

od klauzuli NOT IN w LINQ do SQL autorstwa Marco Russo


Ale używam linq do encji, więc otrzymuję „tylko pierwotne typy mogą być użyte błąd”. Czy jest coś do obejrzenia ...? oprócz ręcznego iterowania i znajdowania listy.
Nowicjusz

13
Działa to dla mnie dobrze z LINQ to Entities. SQL staje się zapytaniem GDZIE NIE ISTNIEJE (podzapytanie). Może była aktualizacja, która rozwiązała ten problem?
scottheckel

2
Myślę, że nowsze wersje EF obsługują. Zawiera, plus to pytanie nie oznacza EF (wersja) ani LinqToSQL .. więc może zaistnieć potrzeba zawężenia zakresu pytania i odpowiedzi tutaj ..
Brett Caswell

4
@Robert Rouse - Link do The Not in cluse w linq to sql już nie działa. Po prostu fyi.
JonH

Podany link prowadzi do strony oznaczonej jako zawierająca złośliwe oprogramowanie.
mikesigs

334

Chcesz operatora z wyjątkiem.

var answer = list1.Except(list2);

Lepsze wyjaśnienie tutaj: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

UWAGA: Ta technika działa najlepiej tylko w przypadku typów pierwotnych, ponieważ należy zaimplementować IEqualityComparer, aby użyć Exceptmetody ze złożonymi typami.


7
Korzystanie z wyjątkiem: jeśli pracujesz z listami typów złożonych, musisz zaimplementować IEqualityComparer <MyComlplexType>, co sprawia, że ​​nie jest to takie miłe
sakito,

4
Nie musisz implementować IEqualityComparer <T>, jeśli chcesz po prostu porównać równość odniesienia lub jeśli przesłoniłeś T.Equals () i T.GetHashCode (). Jeśli nie zaimplementujesz IEqualityComparer <T>, zostanie użyty EqualityComparer <T>. Domyślny .
piedar

2
@Echostorm (i inni czytający), jeśli wykonasz obiekt Select to Anonymous, HashCode zostanie określony przez wartości właściwości; list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));jest to szczególnie przydatne, gdy określasz równość, oceniając tylko zestaw wartości typu złożonego.
Brett Caswell

3
Właściwie ktoś wskazał poniżej i myślę słusznie, że nie byłoby potrzeby implementować IEquatityComparor<T,T>ani zastępować metod porównywania obiektów w LinqToSqlscenariuszu; ponieważ zapytanie będzie reprezentowane jako / skompilowane do / wyrażone jako SQL; dlatego wartości będą sprawdzane, a nie odwołanie do obiektu.
Brett Caswell

2
Za pomocą exceptI udało mi się przyspieszyć zapytanie LINQ z 8-10 sekund do pół sekundy
Michael Kniskern

61

Dla osób, które zaczynają od grupy obiektów w pamięci i wykonują zapytania do bazy danych, uważam, że to najlepszy sposób:

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

To tworzy ładną WHERE ... IN (...)klauzulę w SQL.


1
tak naprawdę można to zrobić w 3.5
George Silva,

59

elementy na pierwszej liście, na których e-mail nie istnieje na drugiej liście.

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

16

Możesz użyć kombinacji Where i Any do znalezienia nie w:

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

8

Możesz wziąć obie kolekcje na dwóch różnych listach, powiedzmy list1 i list2.

Więc po prostu napisz

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

To zadziała.


3
Fajnie, ale ma efekt uboczny usuwania elementów z listy.
Tarik

7

W przypadku, gdy używa się ADO.NET Entity Framework , rozwiązanie EchoStorm również działa idealnie. Ale zajęło mi to kilka minut, aby owinąć wokół niego głowę. Zakładając, że masz kontekst bazy danych, dc i chcesz znaleźć wiersze w tabeli x niepołączone w tabeli y, pełna odpowiedź brzmi:

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

W odpowiedzi na komentarz Andy'ego tak, w zapytaniu LINQ można mieć dwa z nich. Oto kompletny przykład działania z wykorzystaniem list. Każda klasa, Foo i Bar, ma identyfikator. Foo ma odniesienie do Bar za pomocą „klucza obcego” za pośrednictwem Foo.BarId. Program wybiera wszystkie Foo niepowiązane z odpowiednim paskiem.

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

czy dwa froms wor w LINQ? to byłoby pomocne.
Andy

Andy: Tak, patrz poprawiona odpowiedź powyżej.
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

Można również użyć All()

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

2

Chociaż Exceptjest częścią odpowiedzi, to nie jest cała odpowiedź. Domyślnie Except(podobnie jak kilku operatorów LINQ) dokonuje porównania referencji dla typów referencji. Aby porównać według wartości w obiektach, musisz

  • zaimplementować IEquatable<T>w swoim typie lub
  • zastąp Equalsi GetHashCodew swoim typie, lub
  • przekazać instancję typu implementującą IEqualityComparer<T>dla danego typu

2
... jeśli mówimy o LINQ do Objects. Jeśli było to LINQ na SQL, zapytanie jest tłumaczone na instrukcje SQL uruchamiane w bazie danych, więc nie ma to zastosowania.
Lucas,

1

Przykład użycia listy int dla uproszczenia.

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

Dla każdego, kto chce również używać INoperatora podobnego do SQL w C #, pobierz ten pakiet:

Mshwf.NiceLinq

Ma Ini NotInmetody:

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

Nawet ty możesz tego użyć w ten sposób

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

0

Dziękuję, Brett. Twoja sugestia też mi pomogła. Miałem listę obiektów i chciałem ją przefiltrować za pomocą innej listy obiektów. Dzięki jeszcze raz....

Jeśli ktoś potrzebuje, spójrz na mój przykładowy kod:

'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

Nie testowałem tego za pomocą LINQ dla podmiotów :

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;

Alternatywnie:

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

Czy nie możesz wykonać zewnętrznego połączenia, wybierając elementy z pierwszej listy tylko wtedy, gdy grupa jest pusta? Coś jak:

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)

Nie jestem pewien, czy działałoby to w jakikolwiek efektywny sposób w ramach Entity.


0

Alternatywnie możesz to zrobić w następujący sposób:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
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.