Як би ви зробили запит "не в" з 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.