2008-10-08 8 views
255

Tengo dos colecciones que tienen la propiedad Email en ambas colecciones. Necesito obtener una lista de los artículos en la primera lista donde Email no existe en la segunda lista. Con SQL, simplemente usaría "no en", pero no sé cuál es el equivalente en LINQ. ¿Cómo se hace eso?¿Cómo harías una consulta "no entre" con LINQ?

hasta ahora tengo una combinació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 unirse ya que 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 hacer eso todavía.

+3

Tenga en cuenta que la respuesta de Echostorm produce código que es mucho más claro de leer que Robert de –

Respuesta

249

No sé si esto va a 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 The NOT IN clause in LINQ to SQL por Marco Russo

+0

pero yo uso LINQ a las entidades, por lo que obtener "únicos tipos primitivos se pueden utilizar de error ". Hay algún trabajo alrededor...? aparte de iterar manualmente y encontrar la lista. – Novice

+21

LINQ to entities doesnot support where! (From .....). Contiene() cláusula .. Tu respuesta es aplicable solo para LINQ TO SQL ... La pregunta que se hace es para LINQ TO Entities ... No sé cómo se hizo el cuestionario aceptó su respuesta y la marcó ...Preferiría pensar en bajar la votación de su respuesta, pero no lo haré ... Aceptar una respuesta incorrecta hace que los usuarios de StackOverFlow empeoren y eso va en contra de las reglas de la comunidad de stackoverflow. Por favor absténgase de tales actos – Ashes

+11

Esto funciona bien para mí con LINQ to Entities. El SQL se convierte en una consulta DONDE NO EXISTE (subconsulta). Tal vez hubo una actualización que se dirigió a esto? – scottheckel

3
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}; 
281

desea que el operador Excepto.

var answer = list1.Except(list2); 

explicación mejor aquí: http://blogs.msdn.com/charlie/archive/2008/07/12/the-linq-set-operators.aspx

NOTA: Esta técnica funciona mejor para sólo los tipos primitivos, ya que hay que poner en práctica un IEqualityComparer utilizar el método con excepción de los tipos complejos.

+6

Usando Excepto: si trabaja con listas de tipos complejos, entonces tiene que implementar un IEqualityComparer , que no es tan bueno –

+4

No ** tiene ** para implementar IEqualityComparer si solo quiere comparar igualdad de referencia o si ha reemplazado a T.Equals() y T.GetHashCode(). Si no implementa IEqualityComparer , se usará [EqualityComparer .Default] (http://stackoverflow.com/a/17225006/1925996). – piedar

+1

@Echostorm (y otros leyendo), si selecciona un objeto Anónimo, el HashCode estará determinado por los valores de la 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 evaluando solo un conjunto de valores del tipo complejo. –

1

Ejemplo usando la lista de int para simplificar.

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

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

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

Mientras Except es parte de la respuesta, no es toda la respuesta. De forma predeterminada, Except (como varios de los operadores LINQ) hace una comparación de referencia sobre los tipos de referencia. Para comparar los valores de los objetos, que tendrá que

  • implementar IEquatable<T> en su tipo, o
  • anulación Equals y GetHashCode en su tipo, o
  • pase en una instancia de un tipo de implementación IEqualityComparer<T> para su tipo
+1

... si estamos hablando de LINQ to Objects. Si fue LINQ to SQL, la consulta se traduce en sentencias SQL que se ejecutan en la base de datos, por lo que esto no se aplica. – Lucas

6

En el caso donde se usa el ADO.NET Entity Framework, la solución de EchoStorm también funciona perfectamente. Pero tardé unos minutos en rodearlo con la cabeza. Asumiendo que tiene un contexto de base de datos, cc, y quiere encontrar registros en la tabla X no vinculada en la tabla y, la respuesta respuesta completa que parece:

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 de en una consulta LINQ. Aquí hay un ejemplo completo de trabajo, usando listas. Cada clase, Foo y Bar, tiene un Id. Foo tiene una referencia de "clave externa" para 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; } 
} 
+0

hacer dos cosas en LINQ? eso sería útil. – Andy

+0

Andy: Sí, ver la respuesta revisada anterior. – Brett

53

Para las personas que se inician con un grupo de objetos en memoria y está consultando contra una base de datos, he encontrado que este es el mejor camino a seguir:

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

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

+1

en realidad, puede hacerlo en 3.5 –

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 necesita, por favor, echar un vistazo a mi código de ejemplo:

'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 
8

que puede tomar tanto las colecciones en dos listas diferentes, dicen lista1 y lista2.

Entonces acaba de escribir

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

Esto funcionará.

+1

Agradable, pero tiene el efecto secundario de eliminar elementos de la lista. – Tarik

0

no he probado 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; 

alternativa:

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

Se puede utilizar una combinación de ¿Dónde y Cualquier para encontrar no en:

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

Mucho mejor ... gracias por compartir – din

0

Couldn ¿Hace una combinación externa, solo seleccionando los elementos de la primera lista si el grupo está vacío? Algo así 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) 

estoy seguro de si esto funcionaría en cualquier tipo de forma eficiente con el marco de la entidad.

1

Para cualquier persona que también quiere utilizar un operador de SQL-IN por igual en C#, descargar este paquete:

Mshwf.NiceLinq

Tiene In y NotIn métodos:

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

Incluso puede usarlo de esta manera

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

Uno podría también utilizar All()

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