คุณจะทำแบบสอบถาม "ไม่ได้อยู่ใน" กับ LINQ อย่างไร


307

ฉันมีสองคอลเล็กชันที่มีคุณสมบัติEmailในทั้งสองคอลเลกชัน ฉันต้องการรับรายการในรายการแรกที่Emailไม่มีอยู่ในรายการที่สอง ด้วย SQL ฉันจะใช้ "ไม่ได้อยู่ใน" แต่ฉันไม่รู้ว่าเทียบเท่าใน LINQ นั่นเป็นวิธีที่ทำ?

จนถึงตอนนี้ฉันได้เข้าร่วม ...

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

แต่ฉันเข้าร่วมไม่ได้เพราะฉันต้องการความแตกต่างและการเข้าร่วมจะล้มเหลว ฉันต้องการวิธีการใช้ประกอบด้วยหรือมีอยู่ฉันเชื่อว่า ฉันยังไม่พบตัวอย่างที่จะทำเช่นนั้น


3
โปรดทราบว่าคำตอบของ Echostorm จะสร้างรหัสที่ชัดเจนกว่าการอ่านของโรเบิร์ต
นาธานคูป

คำตอบ:


302

ฉันไม่รู้ว่าจะช่วยคุณได้หรือไม่ ..

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

จากประโยค NOT IN ใน LINQ ไปยัง SQLโดยMarco Russo


แต่ฉันใช้ linq กับเอนทิตีดังนั้นฉันได้รับ "ข้อผิดพลาดประเภทดั้งเดิมเท่านั้นที่สามารถใช้งานได้" มีวิธีแก้ไขไหม ... นอกเหนือจากการวนซ้ำด้วยตนเองและค้นหารายการ
สามเณร

13
มันใช้งานได้ดีสำหรับฉันด้วย LINQ กับหน่วยงาน SQL จะกลายเป็นแบบสอบถามที่ไม่มีอยู่ (แบบสอบถามย่อย) อาจมีการอัปเดตที่จัดการเรื่องนี้หรือไม่
scottheckel

2
ฉันคิดว่ารุ่นใหม่ของ EF ให้การสนับสนุนรวมถึงคำถามนี้ไม่ได้ติดแท็ก EF (รุ่น) หรือ LinqToSQL .. ดังนั้นอาจจำเป็นต้องกำหนดขอบเขตของคำถามและคำตอบที่นี่ ..
Brett Caswell

4
@Robert Rouse - ลิงก์ไปยัง The Not in cluse ใน linq to sql ไม่ทำงานอีกต่อไป เพียงแค่ fyi
JonH

ลิงก์ที่ให้นั้นนำไปสู่ไซต์ที่ถูกตั้งค่าสถานะว่ามีมัลแวร์
mikesigs

334

คุณต้องการตัวดำเนินการยกเว้น

var answer = list1.Except(list2);

คำอธิบายที่ดีขึ้นที่นี่: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

หมายเหตุ:เทคนิคนี้ทำงานได้ดีที่สุดสำหรับประเภทดั้งเดิมเท่านั้นเนื่องจากคุณต้องใช้IEqualityComparerเพื่อใช้Exceptวิธีที่มีประเภทที่ซับซ้อน


7
ใช้ยกเว้นหากคุณทำงานกับรายการประเภทที่ซับซ้อนแล้วคุณจะต้องดำเนินการ IEqualityComparer <MyComlplexType> ซึ่งก็จะทำให้มันไม่ว่าดี
sakito

4
คุณไม่จำเป็นต้องใช้ IEqualityComparer <T> หากคุณต้องการเปรียบเทียบความเท่าเทียมกันของการอ้างอิงหรือถ้าคุณแทนที่ T.Equals () และ T.GetHashCode () หากคุณไม่ใช้ IEqualityComparer <T> EqualityComparer <T>จะใช้ค่าเริ่มต้น
piedar

2
@Echostorm (และการอ่านอื่น ๆ ) หากคุณทำวัตถุ Select to Anonymous HashCode จะถูกกำหนดโดยค่าคุณสมบัติ list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));สิ่งนี้มีประโยชน์โดยเฉพาะอย่างยิ่งเมื่อคุณกำหนดความเท่าเทียมกันโดยการประเมินชุดของประเภทที่ซับซ้อนเท่านั้น
Brett Caswell

3
จริงๆแล้วมีคนชี้ให้เห็นด้านล่างและฉันคิดว่าถูกต้องว่าไม่จำเป็นต้องใช้IEquatityComparor<T,T>หรือแทนที่วิธีการเปรียบเทียบวัตถุในLinqToSqlสถานการณ์ สำหรับแบบสอบถามจะแสดงเป็น / รวบรวมเพื่อ / แสดงเป็น SQL; ดังนั้นค่าจะถูกตรวจสอบไม่ใช่การอ้างอิงวัตถุ
Brett Caswell

2
การใช้exceptฉันสามารถเพิ่มความเร็วการสืบค้น LINQ จาก 8-10 วินาทีถึงครึ่งวินาที
Michael Kniskern

61

สำหรับผู้ที่เริ่มต้นด้วยกลุ่มของวัตถุในหน่วยความจำและกำลังค้นหากับฐานข้อมูลฉันพบว่านี่เป็นวิธีที่ดีที่สุดในการไป:

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

สิ่งนี้สร้างWHERE ... IN (...)ประโยคที่ดีใน SQL


1
ที่จริงคุณสามารถทำได้ใน 3.5
George Silva

59

รายการในรายการแรกที่ไม่มีอีเมลในรายการที่สอง

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

16

คุณสามารถใช้การรวมกันของ Where and Any สำหรับการค้นหาที่ไม่อยู่ใน

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

8

คุณสามารถนำทั้งสองชุดมารวมกันในสองรายการที่ต่างกันพูด list1 และ list2

จากนั้นเพียงแค่เขียน

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

สิ่งนี้จะได้ผล


3
ดี แต่มีผลข้างเคียงของการลบองค์ประกอบออกจากรายการ
Tarik

7

ในกรณีที่ใช้ADO.NET Entity Frameworkโซลูชันของ EchoStorm ก็ทำงานได้อย่างสมบูรณ์ แต่ฉันใช้เวลาไม่กี่นาทีเพื่อพันหัวฉัน สมมติว่าคุณมีบริบทฐานข้อมูล, dc และต้องการหาแถวในตาราง x ที่ไม่ได้เชื่อมโยงในตาราง y คำตอบที่สมบูรณ์ดูเหมือนจะเป็น:

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

ในการตอบสนองต่อความคิดเห็นของแอนดี้ใช่หนึ่งสามารถมีสองจากในแบบสอบถาม LINQ นี่คือตัวอย่างการทำงานที่สมบูรณ์โดยใช้รายการ แต่ละชั้น Foo and Bar มีรหัส Foo มีการอ้างอิง "foreign key" ไปที่ Bar ผ่าน Foo.BarId โปรแกรมเลือก Foo ทั้งหมดที่ไม่ได้เชื่อมโยงกับบาร์ที่เกี่ยวข้อง

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

ทำสองสิ่งที่ต้องกังวลใน LINQ ไหม ที่จะเป็นประโยชน์
Andy

แอนดี้: ใช่เห็นคำตอบที่แก้ไขแล้วข้างต้น
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};


2

ในขณะที่Exceptเป็นส่วนหนึ่งของคำตอบมันไม่ใช่คำตอบทั้งหมด โดยค่าเริ่มต้นExcept(เช่นเดียวกับตัวดำเนินการ LINQ หลายแห่ง) ทำการเปรียบเทียบการอ้างอิงกับประเภทการอ้างอิง ในการเปรียบเทียบตามค่าในวัตถุคุณจะต้อง

  • ใช้งานIEquatable<T>ในประเภทของคุณหรือ
  • แทนที่EqualsและGetHashCodeในประเภทของคุณหรือ
  • ส่งผ่านอินสแตนซ์ของประเภทที่ใช้งานIEqualityComparer<T>กับประเภทของคุณ

2
... ถ้าเรากำลังพูดถึง LINQ กับวัตถุ ถ้าเป็น LINQ ไปยัง SQL เคียวรีจะถูกแปลเป็นคำสั่ง SQL ที่รันบนฐานข้อมูลดังนั้นจึงไม่มีผลบังคับใช้
ลูคัส

1

ตัวอย่างการใช้ List of 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

สำหรับผู้ที่ต้องการใช้ตัวINดำเนินการที่เหมือนกันของ SQL ใน C # ให้ดาวน์โหลดแพ็คเกจนี้:

Mshwf.NiceLinq

มันมีInและNotInวิธีการ:

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

แม้คุณสามารถใช้วิธีนี้

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

0

ขอบคุณเบรต ข้อเสนอแนะของคุณช่วยฉันด้วย ฉันมีรายการของวัตถุและต้องการกรองโดยใช้รายการวัตถุอื่น ขอบคุณอีกครั้ง....

หากใครต้องการโปรดดูตัวอย่างรหัสของฉัน:

'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

ฉันไม่ได้ทดสอบสิ่งนี้กับLINQ กับหน่วยงาน :

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;

อีกวิธีหนึ่งคือ:

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

คุณไม่สามารถทำการรวมภายนอกได้เพียงเลือกรายการจากรายการแรกหากกลุ่มว่างเปล่า สิ่งที่ต้องการ:

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)

ฉันไม่แน่ใจว่าสิ่งนี้จะทำงานในรูปแบบที่มีประสิทธิภาพใด ๆ กับ Entity framework หรือไม่


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.