¿Cómo haría una consulta "no en" con LINQ?

307

Tengo dos colecciones que tienen propiedades Emailen ambas colecciones. Necesito obtener una lista de los elementos en la primera lista donde Emailno existe en la segunda lista. Con SQL simplemente usaría "no en", pero no sé el equivalente en LINQ. ¿Cómo se hace eso?

Hasta ahora tengo una unión, como ...

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

Pero no puedo unirme porque necesito la diferencia y la unión fallaría. Necesito alguna forma de usar Contiene o Existe, creo. Simplemente no he encontrado un ejemplo para hacerlo todavía.

Brennan
fuente
3
Tenga en cuenta que la respuesta de Echostorm produce un código que es mucho más claro de leer que el de Robert
Nathan Koop

Respuestas:

302

No sé si esto te ayudará pero ...

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

de la cláusula NOT IN en LINQ a SQL por Marco Russo

Robert Rouse
fuente
Pero uso linq para entidades, por lo que obtengo "solo los tipos primitivos se pueden usar error". Hay algún trabajo alrededor...? aparte de iterar manualmente y encontrar la lista.
Novato
13
Esto funciona bien para mí con LINQ to Entities. El SQL se convierte en una consulta WHERE NOT EXISTS (subconsulta). ¿Tal vez hubo una actualización que abordó esto?
scottheckel
2
Creo que las versiones más nuevas de EF son compatibles. Contiene, además de que esta pregunta no etiqueta EF (versión) o LinqToSQL ... por lo que puede ser necesario analizar la pregunta y responder aquí ...
Brett Caswell
44
@Robert Rouse: el enlace a The Not in cluse in linq to sql ya no funciona. Solo un para tu información.
JonH
El enlace proporcionado conduce a un sitio marcado como que contiene malware.
mikesigs
334

Desea el operador Excepto.

var answer = list1.Except(list2);

Mejor explicación aquí: https://docs.microsoft.com/archive/blogs/charlie/linq-farm-more-on-set-operators

NOTA: Esta técnica funciona mejor solo para tipos primitivos, ya que debe implementar un IEqualityComparer para usar el Exceptmétodo con tipos complejos.

Echostorm
fuente
77
Usando Excepto: Si trabaja con listas de tipos complejos, entonces usted tiene que poner en práctica un IEqualityComparer <MyComlplexType>, la cual no hace que agradable
sakito
44
No tiene que implementar IEqualityComparer <T> si solo desea comparar la igualdad de referencia o si ha anulado T.Equals () y T.GetHashCode (). Si no implementa IEqualityComparer <T>, se utilizará EqualityComparer <T> .Default .
piedar
2
@Echostorm (y otros que leen), si hace un objeto Seleccionar a anónimo, el HashCode estará determinado por los valores de propiedad; list1.Select(item => new { Property1 = item.Property1, Property2 = item.Property2 }).Except(list2.Select( item => new { Property1 = item.Property1, Property2 = item.Property2 }));Esto es particularmente útil cuando se determina la igualdad al evaluar solo un conjunto de valores del tipo complejo.
Brett Caswell
3
En realidad, alguien señaló a continuación, y pienso correctamente, que no sería necesario implementar IEquatityComparor<T,T>o anular los métodos de comparación de objetos en un LinqToSqlescenario; para, la consulta se representará como / compilado a / expresado como SQL; así se verificarán los valores, no la referencia del objeto.
Brett Caswell
2
Utilizando el exceptpude acelerar una consulta LINQ de 8-10 segundos a medio segundo
Michael Kniskern
61

Para las personas que comienzan con un grupo de objetos en memoria y realizan consultas en una base de datos, he encontrado que esta es la mejor manera de hacerlo:

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

Esto produce una bonita WHERE ... IN (...)cláusula en SQL.

StriplingWarrior
fuente
1
en realidad, puedes hacer eso en 3.5
George Silva
59

elementos en la primera lista donde el correo electrónico no existe en la segunda lista.

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

Puede usar una combinación de Where y Any para encontrar no en:

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

Puede tomar ambas colecciones en dos listas diferentes, digamos list1 y list2.

Entonces solo escribe

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

Esto funcionará

Chintan Udeshi
fuente
3
Agradable pero tiene el efecto secundario de eliminar elementos de la lista.
Tarik
7

En el caso de que uno esté usando el Marco de entidades ADO.NET , la solución de EchoStorm también funciona perfectamente. Pero me llevó unos minutos entenderlo. Suponiendo que tiene un contexto de base de datos, cc, y desea encontrar filas en la tabla x no vinculadas en la tabla y, la respuesta de respuesta completa se ve así:

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

En respuesta al comentario de Andy, sí, uno puede tener dos de en una consulta LINQ. Aquí hay un ejemplo de trabajo completo, usando listas. Cada clase, Foo y Bar, tiene una identificación. Foo tiene una referencia de "clave externa" a Bar a través de Foo.BarId. El programa selecciona todos los Foo no vinculados a una barra correspondiente.

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
fuente
Qué hacen dos froms en LINQ? Eso sería útil.
Andy
Andy: Sí, ver la respuesta revisada arriba.
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};
tvanfosson
fuente
4

También se podría usar All()

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

Si bien Exceptes parte de la respuesta, no es la respuesta completa. Por defecto, Except(como varios de los operadores LINQ) hace una comparación de referencia en los tipos de referencia. Para comparar por valores en los objetos, deberá

  • implementar IEquatable<T>en su tipo, o
  • anular Equalsy GetHashCodeen su tipo, o
  • pasar en una instancia de un tipo de implementación IEqualityComparer<T>para su tipo
Ryan Lundy
fuente
2
... si estamos hablando de LINQ to Objects. Si fue LINQ to SQL, la consulta se traduce en declaraciones SQL que se ejecutan en la base de datos, por lo que esto no se aplica.
Lucas
1

Ejemplo usando List of int por simplicidad.

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
fuente
1

Para cualquiera que también quiera usar un INoperador similar a SQL en C #, descargue este paquete:

Mshwf.NiceLinq

Tiene Iny NotInmétodos:

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

Incluso puedes usarlo de esta manera

var result = list1.In(x => x.Email, "[email protected]", "[email protected]", "[email protected]");
mshwf
fuente
0

Gracias Brett Tu sugerencia también me ayudó. Tenía una lista de objetos y quería filtrar eso usando otra lista de objetos. Gracias de nuevo....

Si alguien lo necesita, eche un vistazo a mi ejemplo de código:

'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
fuente
0

No probé esto con 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;

Alternativamente:

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 );
Tarik
fuente
0

¿No podría hacer una unión externa, solo seleccionando los elementos de la primera lista si el grupo está vacío? Algo como:

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)

No estoy seguro de si esto funcionaría de alguna manera eficiente con el marco Entity.

Marten Jacobs
fuente
0

Alternativamente, puede hacer así:

var result = list1.Where(p => list2.All(x => x.Id != p.Id));
nzrytmn
fuente