Як би ви зробили запит "не в" з 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
Зауважте, що відповідь Ехосторму дає чіткіший для читання код, ніж Роберт
Натан Кооп

Відповіді:


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 для Entities. SQL стає запитом, де не існує (підзапит) запиту. Можливо, було оновлення, яке вирішило це?
scottheckel

2
Я думаю , що нові версії EF Підтримує .Contains, плюс це питання не тег EF (версія) або LinqToSql .. так що може виникнути необхідність в обсяг питання і відповідь тут ..
Бретт Caswell

4
@Robert Rouse - Посилання на The Not in the claus in linkq to sql більше не працює. Просто фій.
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> .Default .
пієдар

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 }));це особливо корисно, коли ви визначаєте рівність, оцінюючи лише набір значень складного типу.
Бретт Касвелл

3
Насправді, хтось вказав нижче, і я вважаю правильно, що не було б необхідності впроваджувати IEquatityComparor<T,T>або переосмислювати методи порівняння об'єктів у LinqToSqlсценарії; для, запит буде представлений у вигляді / складений / виражений у вигляді SQL; таким чином буде перевірено значення, а не посилання на об'єкт.
Бретт Касуелл

2
Використовуючи exceptI, я зміг пришвидшити запит 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
Джордж Сільва

59

елементи в першому списку, де електронної пошти не існує у другому списку.

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

16

Ви можете використовувати комбінацію "Де" і "Будь-яке", щоб знайти не в:

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

8

Ви можете взяти обидві колекції у двох різних списках, скажімо, list1 та list2.

Тоді просто напишіть

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

Це спрацює.


3
Приємно, але має побічний ефект від видалення елементів зі списку.
Тарік

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 і Bar, має ідентифікаційний номер. Foo має "зовнішній ключ" посилання на 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? це було б корисно.
Енді

Енді: Так, див. Переглянуту відповідь вище.
Бретт

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

Можна також скористатися All()

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

2

Хоча Exceptце частина відповіді, це не вся відповідь. За замовчуванням Except(як і кілька операторів LINQ) проводиться порівняння посилань на типи посилань. Для порівняння за значеннями в об’єктах вам доведеться

  • реалізувати IEquatable<T>у своєму типі, або
  • переосмислити Equalsі GetHashCodeу своєму типі, або
  • передайте екземпляр типу, що реалізується IEqualityComparer<T>для вашого типу

2
... якщо ми говоримо про LINQ до об'єктів. Якщо це був LINQ в SQL, запит переводиться на оператори SQL, які працюють у базі даних, тому це не застосовується.
Лукас

1

Приклад за допомогою списку 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 для 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;

Як варіант:

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.


0

Або ви можете зробити так:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.