Bagaimana Anda melakukan kueri "tidak dalam" dengan LINQ?

307

Saya memiliki dua koleksi yang memiliki properti Emaildi kedua koleksi. Saya perlu mendapatkan daftar item di daftar pertama di mana Emailtidak ada di daftar kedua. Dengan SQL saya hanya akan menggunakan "tidak dalam", tapi saya tidak tahu padanan dalam LINQ. Bagaimana itu dilakukan?

Sejauh ini saya sudah bergabung, seperti ...

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

Tapi saya tidak bisa bergabung karena saya butuh perbedaan dan bergabung akan gagal. Saya perlu beberapa cara menggunakan Berisi atau Ada saya percaya. Saya belum menemukan contoh untuk melakukan itu.

Brennan
sumber
3
Harap perhatikan bahwa jawaban Echostorm menghasilkan kode yang jauh lebih jelas untuk dibaca daripada jawaban Robert
Nathan Koop

Jawaban:

302

Saya tidak tahu apakah ini akan membantu Anda tetapi ..

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

dari klausa NOT IN di LINQ ke SQL oleh Marco Russo

Robert Rouse
sumber
Tetapi saya menggunakan LINQ untuk entitas, jadi saya mendapatkan "hanya tipe primitif yang dapat digunakan kesalahan". Apakah ada pekerjaan di sekitar ...? selain dari iterasi dan menemukan daftar secara manual.
Pemula
13
Ini berfungsi baik untuk saya dengan LINQ to Entities. SQL menjadi permintaan WHERE NOT EXISTS (subquery). Mungkin ada pembaruan yang membahas hal ini?
scottheckel
2
Saya pikir versi EF yang lebih baru mendukung. Berisi, ditambah pertanyaan ini tidak memberi tanda pada EF (versi) atau LinqToSQL .. jadi mungkin perlu ada cakupan pertanyaan dan jawaban di sini ..
Brett Caswell
4
@Robert Rouse - Link ke The Not in cluse in linq to sql tidak lagi berfungsi. Hanya sebuah fyi.
JonH
Tautan yang diberikan mengarah ke situs yang ditandai mengandung malware.
mikesigs
334

Anda menginginkan operator Kecuali.

var answer = list1.Except(list2);

Penjelasan yang lebih baik di sini: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

CATATAN: Teknik ini hanya berfungsi untuk tipe primitif saja, karena Anda harus mengimplementasikan IEqualityComparer untuk menggunakan Exceptmetode dengan tipe kompleks.

Echostorm
sumber
7
Menggunakan Kecuali: Jika Anda bekerja dengan daftar jenis yang kompleks, maka Anda harus mengimplementasikan IEqualityComparer <MyComlplexType>, yang membuatnya tidak begitu bagus
sakito
4
Anda tidak harus mengimplementasikan IEqualityComparer <T> jika Anda hanya ingin membandingkan persamaan referensi atau jika Anda telah menimpa T.Equals () dan T.GetHashCode (). Jika Anda tidak menerapkan IEqualityComparer <T>, EqualityComparer <T> .Default akan digunakan.
piedar
2
@Echostorm (dan yang lainnya membaca), jika Anda melakukan objek Select to Anonymous, HashCode akan ditentukan oleh nilai properti; list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));ini sangat berguna ketika Anda menentukan kesetaraan dengan mengevaluasi hanya satu set nilai dari tipe kompleks.
Brett Caswell
3
Sebenarnya, seseorang menunjukkan di bawah ini, dan saya pikir dengan benar, bahwa tidak akan ada kebutuhan untuk mengimplementasikan IEquatityComparor<T,T>atau mengganti metode perbandingan objek dalam LinqToSqlskenario; untuk, permintaan akan direpresentasikan sebagai / dikompilasi ke / dinyatakan sebagai SQL; dengan demikian nilai akan diperiksa, bukan referensi objek.
Brett Caswell
2
Menggunakan exceptsaya dapat mempercepat permintaan LINQ dari 8-10 detik menjadi setengah detik
Michael Kniskern
61

Untuk orang-orang yang mulai dengan sekelompok objek di-memori dan menanyakan terhadap database, saya telah menemukan ini sebagai cara terbaik untuk pergi:

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

Ini menghasilkan WHERE ... IN (...)klausa yang bagus dalam SQL.

StriplingWarrior
sumber
1
sebenarnya, Anda dapat melakukannya dalam 3,5
George Silva
59

item dalam daftar pertama di mana Email tidak ada di daftar kedua.

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

Anda dapat menggunakan kombinasi Where and Any untuk menemukan yang tidak ada di:

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

Anda dapat mengambil kedua koleksi dalam dua daftar berbeda, misalkan list1 dan list2.

Maka cukup tulis

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

Ini akan bekerja

Chintan Udeshi
sumber
3
Bagus tetapi memiliki efek samping menghapus elemen dari daftar.
Tarik
7

Jika seseorang menggunakan ADO.NET Entity Framework , solusi EchoStorm juga berfungsi dengan baik. Tapi aku butuh beberapa menit untuk melilitkan kepalaku. Dengan asumsi Anda memiliki konteks basis data, dc, dan ingin menemukan baris dalam tabel x tidak tertaut pada tabel y, jawaban jawaban lengkapnya tampak seperti:

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

Menanggapi komentar Andy, ya, seseorang dapat memiliki dua dari dalam kueri LINQ. Ini contoh kerja yang lengkap, menggunakan daftar. Setiap kelas, Foo and Bar, memiliki ID. Foo memiliki referensi "kunci asing" ke Bar melalui Foo.BarId. Program memilih semua Foo yang tidak ditautkan ke Bar yang sesuai.

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; }
}
Brett
sumber
melakukan dua dari wor di LINQ? itu akan sangat membantu.
Andy
Andy: Ya, lihat jawaban yang direvisi di atas.
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};
tvanfosson
sumber
4

Satu juga bisa digunakan All()

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

Meskipun Exceptmerupakan bagian dari jawaban, itu bukan jawaban keseluruhan. Secara default, Except(seperti beberapa operator LINQ) melakukan perbandingan referensi pada jenis referensi. Untuk membandingkan dengan nilai dalam objek, Anda harus

  • melaksanakan IEquatable<T> tipe Anda, atau
  • timpa EqualsdanGetHashCode dalam tipe Anda, atau
  • mengirimkan instance penerapan tipe IEqualityComparer<T>untuk tipe Anda
Ryan Lundy
sumber
2
... jika kita berbicara tentang LINQ ke Objek. Jika itu LINQ ke SQL, kueri diterjemahkan ke dalam pernyataan SQL yang berjalan di database, jadi ini tidak berlaku.
Lucas
1

Contoh menggunakan Daftar int untuk kesederhanaan.

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());
Inisheer
sumber
1

Bagi siapa saja yang juga ingin menggunakan INoperator yang mirip SQL di C #, unduh paket ini:

Mshwf.NiceLinq

Itu memiliki Indan NotInmetode:

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

Bahkan Anda bisa menggunakannya dengan cara ini

var result = list1.In(x => x.Email, "[email protected]", "[email protected]", "[email protected]");
mshwf
sumber
0

Terima kasih, Brett. Saran Anda juga membantu saya. Saya punya daftar Objek dan ingin memfilter menggunakan daftar objek lain. Terima kasih lagi....

Jika ada yang butuh, silakan lihat contoh kode saya:

'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
mangeshkt
sumber
0

Saya tidak menguji ini dengan LINQ ke Entitas :

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;

Kalau tidak:

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 );
Tarik
sumber
0

Tidak bisakah Anda melakukan gabungan luar, hanya memilih item dari daftar pertama jika grup kosong? Sesuatu seperti:

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)

Saya tidak yakin apakah ini akan bekerja dengan cara efisien apa pun dengan kerangka kerja Entity.

Marten Jacobs
sumber
0

Atau Anda dapat melakukannya seperti ini:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
nzrytmn
sumber