Как бы вы сделали запрос «не в» с LINQ?

307

У меня есть две коллекции, которые имеют свойство Email в обеих коллекциях. Мне нужно получить список элементов в первом списке, где Emailне существует во втором списке. С SQL я бы просто использовал «not in», но я не знаю эквивалента в LINQ. Как это сделать?

Пока у меня есть соединение, как ...

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

Но я не могу присоединиться, потому что мне нужна разница, и соединение не удастся. Мне нужен какой-то способ использования Contains или Exists, которому я верю. Я просто еще не нашел пример, чтобы сделать это.

Бреннан
источник
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 по Марко Руссо

Роберт Роз
источник
Но я использую linq для сущностей, поэтому я получаю "только примитивные типы могут быть использованы ошибки". Есть ли работа вокруг ...? кроме ручной итерации и поиска списка.
Новичок
13
Это прекрасно работает для меня с LINQ to Entities. SQL становится запросом WHERE NOT EXISTS (подзапрос). Может быть, появилось обновление, адресованное этому?
scottheckel
2
Я думаю, что более новые версии EF поддерживают .Contains, плюс этот вопрос не помечает EF (версию) или LinqToSQL ... поэтому может возникнуть необходимость в том, чтобы охватить этот вопрос и ответить на него здесь.
Бретт Касвел
4
@Robert Rouse - ссылка на Not in cluse в linq to sql больше не работает. Просто к вашему сведению.
Джон
Предоставленная ссылка ведет на сайт, помеченный как содержащий вредоносное ПО.
Mikesigs
334

Вы хотите, кроме оператора.

var answer = list1.Except(list2);

Лучшее объяснение здесь: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

ПРИМЕЧАНИЕ. Этот метод лучше всего подходит только для примитивных типов, поскольку для использования метода со сложными типами необходимо реализовать IEqualityComparerExcept .

Echostorm
источник
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 секунд до полсекунды
Майкл Книскерн,
61

Для людей, которые начинают с группы объектов в памяти и опрашивают базу данных, я считаю, что это лучший способ:

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

Это приводит к хорошему WHERE ... IN (...)предложению в SQL.

StriplingWarrior
источник
1
на самом деле, вы можете сделать это в 3,5
Джордж Сильва
59

элементы в первом списке, где Email не существует во втором списке.

from item1 in List1
where !(list2.Any(item2 => item2.Email == item1.Email))
select item1;
Эми Б
источник
16

Вы можете использовать комбинацию Where и Any для поиска не в:

var NotInRecord =list1.Where(p => !list2.Any(p2 => p2.Email  == p.Email));
DevT
источник
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 может быть два оператора from. Вот полный рабочий пример использования списков. Каждый класс, 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; }
}
Brett
источник
сделать два запроса в 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};
tvanfosson
источник
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 to Objects. Если это LINQ to 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

Для тех, кто также хочет использовать похожий на SQL INоператор в 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");
mshwf
источник
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
mangeshkt
источник
0

Я не проверял это с 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;

В качестве альтернативы:

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));
nzrytmn
источник