Come faresti una query "non in" con LINQ?


307

Ho due raccolte che hanno proprietà Emailin entrambe le raccolte. Ho bisogno di ottenere un elenco degli elementi nel primo elenco dove Emailnon esiste nel secondo elenco. Con SQL userei semplicemente "non in", ma non conosco l'equivalente in LINQ. Come è fatto?

Finora ho un join, come ...

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

Ma non posso unirmi poiché ho bisogno della differenza e l'unione fallirebbe. Ho bisogno di un modo di usare Contiene o esiste credo. Non ho ancora trovato un esempio per farlo.


3
Si noti che la risposta di Echostorm produce un codice che è molto più chiaro da leggere rispetto a quello di Robert
Nathan Koop,

Risposte:


302

Non so se questo ti aiuterà, ma ..

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

dalla clausola NOT IN di LINQ a SQL di Marco Russo


Ma io uso linq alle entità, quindi ottengo "solo i tipi primitivi possono essere utilizzati errore". C'è qualche soluzione in giro ...? oltre a iterare manualmente e trovare l'elenco.
Novizio

13
Questo funziona bene per me con LINQ to Entities. L'SQL diventa una query WHERE NOT EXISTS (sottoquery). Forse c'è stato un aggiornamento che ha risolto questo problema?
scottheckel,

2
Penso che le nuove versioni di EF supportino. Contiene, inoltre questa domanda non tagga EF (versione) o LinqToSQL .. quindi potrebbe essere necessario chiarire la domanda e rispondere qui ...
Brett Caswell

4
@Robert Rouse - Il collegamento a The Not in cluse in linq to sql non funziona più. Solo un amico.
JonH

Il collegamento fornito porta a un sito contrassegnato come contenente malware.
mikesigs,

334

Vuoi l'operatore Tranne.

var answer = list1.Except(list2);

Migliore spiegazione qui: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

NOTA: questa tecnica funziona in modo ottimale solo per i tipi primitivi, poiché è necessario implementare un IEqualityComparer per utilizzare il Exceptmetodo con tipi complessi.


7
Utilizzo di Tranne: se lavori con elenchi di tipi complessi, devi implementare un IEqualityComparer <MyComlplexType>, che non lo rende così carino
sakito,

4
Non è necessario implementare IEqualityComparer <T> se si desidera solo confrontare l'uguaglianza di riferimento o se si è ignorato T.Equals () e T.GetHashCode (). Se non si implementa IEqualityComparer <T>, verrà utilizzato EqualityComparer <T> .Default .
piedar

2
@Echostorm (e altri che leggono), se si esegue un oggetto Seleziona su Anonimo, l'HashCode sarà determinato dai valori della proprietà; list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));questo è particolarmente utile quando si determina l'uguaglianza valutando solo un insieme di valori di tipo complesso.
Brett Caswell,

3
In realtà, qualcuno ha sottolineato di seguito, e penso correttamente, che non ci sarebbe la necessità di implementare IEquatityComparor<T,T>o scavalcare i metodi di confronto degli oggetti in uno LinqToSqlscenario; per, la query sarà rappresentata come / compilata in / espressa come SQL; pertanto verranno controllati i valori, non il riferimento all'oggetto.
Brett Caswell,

2
Usando exceptsono stato in grado di velocizzare una query LINQ da 8-10 secondi a mezzo secondo
Michael Kniskern,

61

Per le persone che iniziano con un gruppo di oggetti in memoria e eseguono query su un database, ho scoperto che questo è il modo migliore di procedere:

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

Questo produce una bella WHERE ... IN (...)clausola in SQL.


1
in realtà, puoi farlo in 3.5
George Silva il

59

elementi nel primo elenco in cui l'e-mail non esiste nel secondo elenco.

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

16

È possibile utilizzare una combinazione di Where and Any per trovare non in:

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

8

Puoi prendere entrambe le raccolte in due elenchi diversi, ad esempio list1 ed list2.

Quindi scrivi

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

Questo funzionerà.


3
Bello ma ha l'effetto collaterale di rimuovere elementi dall'elenco.
Tarik,

7

Nel caso in cui si stia utilizzando ADO.NET Entity Framework , anche la soluzione di EchoStorm funziona perfettamente. Ma mi ci sono voluti alcuni minuti per avvolgerci la testa. Supponendo di avere un contesto di database, dc e di voler trovare righe nella tabella x non collegate nella tabella y, la risposta completa alla risposta è simile a:

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

In risposta al commento di Andy, sì, si possono avere due da in una query LINQ. Ecco un esempio di lavoro completo, usando le liste. Ogni classe, Foo and Bar, ha un ID. Foo ha un riferimento "chiave esterna" a Bar tramite Foo.BarId. Il programma seleziona tutti i Foo non collegati a una barra corrispondente.

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

fanno due di wor in LINQ? sarebbe utile.
Andy,

Andy: Sì, vedi la risposta rivista sopra.
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

Si potrebbe anche usare All()

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

2

Mentre Exceptfa parte della risposta, non è l'intera risposta. Per impostazione predefinita, Except(come molti degli operatori LINQ) esegue un confronto di riferimento sui tipi di riferimento. Per confrontare i valori negli oggetti, dovrai farlo

  • strumento IEquatable<T> nel tuo tipo, oppure
  • sovrascrivere EqualseGetHashCode nel tuo tipo, oppure
  • passare un'istanza di un tipo che implementa IEqualityComparer<T>per il tuo tipo

2
... se stiamo parlando di LINQ to Objects. Se era LINQ to SQL, la query viene tradotta in istruzioni SQL eseguite sul database, quindi non si applica.
Lucas,

1

Esempio usando Elenco di int per semplicità.

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

Per chiunque desideri anche utilizzare un INoperatore simile a SQL in C #, scarica questo pacchetto:

Mshwf.NiceLinq

Ha Ine NotInmetodi:

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

Anche tu puoi usarlo in questo modo

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

0

Grazie Brett. Il tuo suggerimento mi è stato di aiuto. Avevo un elenco di oggetti e volevo filtrarlo usando un altro elenco di oggetti. Grazie ancora....

Se qualcuno ha bisogno, dai un'occhiata al mio esempio di codice:

'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

Non ho verificato questo con LINQ to Entities :

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;

In alternativa:

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

Non potresti creare un join esterno, selezionando gli elementi dal primo elenco solo se il gruppo è vuoto? Qualcosa di simile a:

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)

Non sono sicuro che funzionerebbe in modo efficiente con il framework Entity.


0

In alternativa puoi fare così:

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