346

I have two collections which have property Email in both collections. I need to get a list of the items in the first list where Email does not exist in the second list. With SQL I would just use "not in", but I do not know the equivalent in LINQ. How is that done?

So far I have a join, like...

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

But I cannot join since I need the difference and the join would fail. I need some way of using Contains or Exists I believe. I just have not found an example to do that yet.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Brennan
  • 11,546
  • 16
  • 64
  • 86

16 Answers16

359

You want the Except operator.

var answer = list1.Except(list2);

Better explanation here: https://learn.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

NOTE: This technique works best for primitive types only, since you have to implement an IEqualityComparer to use the Except method with complex types.

Callum Watkins
  • 2,844
  • 4
  • 29
  • 49
Echostorm
  • 9,678
  • 8
  • 36
  • 50
  • 8
    Using Except: If you work with complex types lists, then you have to implement an IEqualityComparer, which it makes it not that nice – jdearana Nov 10 '10 at 16:47
  • 4
    You don't **have** to implement IEqualityComparer if you just want to compare reference equality or if you've overridden T.Equals() and T.GetHashCode(). If you don't implement IEqualityComparer, [EqualityComparer.Default](http://stackoverflow.com/a/17225006/1925996) will be used. – piedar Nov 07 '13 at 16:36
  • 3
    @Echostorm (and others reading), if you do a Select to Anonymous object, the HashCode will be determined by the property values; `list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));` this is particular useful when you're determining equality by evaluating only a set of values of the complex type. – Brett Caswell Nov 22 '15 at 19:35
  • 3
    Actually, someone pointed out below, and I think correctly, that there wouldn't be a need to implement `IEquatityComparor` or override object comparison methods in a `LinqToSql` scenario; for, the query will be represented as/compiled to/expressed as SQL; thus the values will be checked, not the object reference. – Brett Caswell Nov 22 '15 at 19:45
  • 3
    Using the `except` I was able to speed up a LINQ query from 8-10 seconds to a half a second – Michael Kniskern Apr 28 '16 at 01:00
  • Was pleasantly surprised when this generated a SQL Server EXCEPT statement for me. Beautiful – Kevin Sep 21 '21 at 00:01
  • Combining the new [`ExceptBy()` starts from .NET 6](https://stackoverflow.com/questions/70135623/net-6-intersectby-and-exceptby-examples) and [`ValueTuple<>`](https://learn.microsoft.com/en-us/dotnet/api/system.valuetuple), we can do `list1.ExceptBy(list2.Select(i => (i.Prop1, i.Prop2)), i => (i.Prop1, i.Prop2))`; – n0099 Jan 11 '23 at 15:09
335

I don't know if this will help you but..

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

from The NOT IN clause in LINQ to SQL by Marco Russo

karel
  • 5,489
  • 46
  • 45
  • 50
Robert Rouse
  • 4,801
  • 1
  • 19
  • 19
  • But i use linq to entities , so i get "only primitive types can be used error". Is there any work around...? apart from manually iterating and finding the list. – Novice Aug 03 '11 at 11:52
  • 13
    This works fine for me with LINQ to Entities. The SQL becomes a WHERE NOT EXISTS(subquery) query. Maybe there was an update that addressed this? – scottheckel Feb 06 '12 at 21:10
  • 3
    I think newer versions of EF do support .Contains, plus this question doesn't tag EF (version) or LinqToSQL.. so there may be a need to scope the question and answer here.. – Brett Caswell Nov 22 '15 at 19:25
  • 5
    @Robert Rouse - The link to The Not in cluse in linq to sql no longer works. Just an fyi. – JonH Feb 04 '16 at 15:15
  • The link provided leads to a site flagged as containing malware. – mikesigs Nov 26 '18 at 23:05
75

For people who start with a group of in-memory objects and are querying against a database, I've found this to be the best way to go:

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

This produces a nice WHERE ... IN (...) clause in SQL.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
62

items in the first list where the Email does not exist in the second list.

from item1 in List1
where !(list2.Any(item2 => item2.Email == item1.Email))
select item1;
Amy B
  • 108,202
  • 21
  • 135
  • 185
21

You can use a combination of Where and Any for finding not in:

var NotInRecord =list1.Where(p => !list2.Any(p2 => p2.Email  == p.Email));
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DevT
  • 4,843
  • 16
  • 59
  • 92
  • Exactly what I was looking for! Whenever I start looking at `.Any()`, especially when it's "NOT ANY" I get confused! – Auspex Jun 15 '22 at 15:54
8

One could also use All()

var notInList = list1.Where(p => list2.All(p2 => p2.Email != p.Email));
Janis S.
  • 2,526
  • 22
  • 32
7

In the case where one is using the ADO.NET Entity Framework, EchoStorm's solution also works perfectly. But it took me a few minutes to wrap my head around it. Assuming you have a database context, dc, and want to find rows in table x not linked in table y, the complete answer answer looks like:

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

In response to Andy's comment, yes, one can have two from's in a LINQ query. Here's a complete working example, using lists. Each class, Foo and Bar, has an Id. Foo has a "foreign key" reference to Bar via Foo.BarId. The program selects all Foo's not linked to a corresponding Bar.

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; }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Brett
  • 8,575
  • 5
  • 38
  • 51
5

You can take both the collections in two different lists, say list1 and list2.

Then just write

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

This will work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
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
  • 524,688
  • 99
  • 697
  • 795
3

While Except is part of the answer, it's not the whole answer. By default, Except (like several of the LINQ operators) does a reference comparison on reference types. To compare by values in the objects, you'll have to

  • implement IEquatable<T> in your type, or
  • override Equals and GetHashCode in your type, or
  • pass in an instance of a type implementing IEqualityComparer<T> for your type
Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
  • 2
    ... if we are talking about LINQ to Objects. If it was LINQ to SQL, the query is translated into SQL statements that run on the database, so this doesn't apply. – Lucas Oct 09 '08 at 21:11
3

Alternatively you can do like this:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
nzrytmn
  • 6,193
  • 1
  • 41
  • 38
1

Example using List of int for simplicity.

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
  • 20,376
  • 9
  • 50
  • 82
1

For anyone who also wants to use a SQL-alike IN operator in C#, download this package :

Mshwf.NiceLinq

It has In and NotIn methods:

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

Even you can use it this way

var result = list1.In(x => x.Email, "a@b.com", "b@c.com", "c@d.com");
mshwf
  • 7,009
  • 12
  • 59
  • 133
0

I did not test this with 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;

Alternatively:

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 );
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tarik
  • 10,810
  • 2
  • 26
  • 40
0

Couldn't you do an outer join, only selecting the items from the first list if the group is empty? Something like:

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)

I'm unsure whether this would work in any sort of efficient way with the Entity framework.

Marten Jacobs
  • 199
  • 1
  • 9
0
 DynamicWebsiteEntities db = new DynamicWebsiteEntities();
    var data = (from dt_sub in db.Subjects_Details
                                //Sub Query - 1
                            let sub_s_g = (from sg in db.Subjects_In_Group
                                           where sg.GroupId == groupId
                                           select sg.SubjectId)
                            //Where Cause
                            where !sub_s_g.Contains(dt_sub.Id) && dt_sub.IsLanguage == false
                            //Order By Cause
                            orderby dt_sub.Subject_Name

                            select dt_sub)
                           .AsEnumerable();
                  
                                SelectList multiSelect = new SelectList(data, "Id", "Subject_Name", selectedValue);

    //======================================OR===========================================

    var data = (from dt_sub in db.Subjects_Details

                               
                            //Where Cause
                            where !(from sg in db.Subjects_In_Group
                                           where sg.GroupId == groupId
                                           select sg.SubjectId).Contains(dt_sub.Id) && dt_sub.IsLanguage == false

                            //Order By Cause
                            orderby dt_sub.Subject_Name

                            select dt_sub)

                           .AsEnumerable();