2011-10-11 14 views
7

Ho due liste, una finta e uno reale, come:Come unire due elenchi in base a una proprietà?

PRIMA

// fake (list 1) 
{ ID = 1, Year = 2011, X = "" } 
, { ID = 2, Year = 2012, X = "" } 
, { ID = 3, Year = 2013, X = "" } 

// real (list 2) 
{ ID = 35, Year = 2011, X = "Information" } 
, { ID = 77, Year = 2013, X = "Important" } 

voglio unirle alla ricerca per l'anno, il risultato dovrebbe essere:

DOPO

{ ID = 35, Year = 2011, X = "Information" } 
, { ID = 2, Year = 2012, X = "" } 
, { ID = 77, Year = 2013, X = "Important" } 

Deve rimuovere elementi con lo stesso anno sulla prima lista e aggiungere l'elemento con l'anno equivalente nella seconda lista alla prima lista, mantenendo l'ordine.

Come posso farlo utilizzando Linq?

+0

forse si può chiarire utilizzando un'illustrazione prima/dopo? mi hai perso completamente ora – sehe

risposta

9

Si dovrebbe essere in grado di farlo utilizzando un "LEFT JOIN": interrogazione

from f in fake 
join r in real 
on f.Year equals r.Year 
into joinResult 
from r in joinResult.DefaultIfEmpty() 
select new 
     { 
      ID = r == null ? f.ID : r.ID, 
      Year = f.Year, 
      X = r == null ? f.X : r.X 
     }; 
+0

L'ultima riga dovrebbe restituire 'f.X' quando la condizione è vera, invece di assumere che sarà sempre la stringa vuota, ma altrimenti, +1 –

+0

@djacobson - Già risolto. –

+0

Forniscici aggiornamenti in stile AJAX per le modifiche alle risposte, COSÌ, seriamente. ;) –

5

di Justin è il modo più efficace per farlo, ma se siete interessati a mantenere oggetti identici (e non la creazione di nuovi record della query) si può fare in questo modo:

var combined = from f in fake 
       let r = (from r1 in real 
         where r1.Year == f.Year 
         select r1).SingleOrDefault() 
       select r ?? f; 
1

Invece di definire l'elenco falso te stesso, provare con LINQ farlo per voi:

Enumerable.Range(2011,3) //2011, 2012, 2013    
      //use the overload that provides a 0-based ordinal position of each element 
      .Select(x,i=> new {ID = i+1, Year = x, X = String.Empty) 
      //now you have your fake list; join with the "real" list based on Year fields, 
      //taking the real element wherever it exists and the fake one otherwise 
      .Join(real, l=>l.Year, r=>r.Year, (l,r) => r == null ? l : r); 

Questo produrrà esattamente il set di risultati desiderato. Probabilmente sarà necessario definire un tipo con nome per gli elementi dell'elenco, tuttavia, poiché non è possibile convertire implicitamente due tipi anonimi definiti separatamente anche se hanno tutti gli stessi tipi di membri/nomi.

1

Utilizzo di IEnumerable.Union e IEqualityComparer.

P.S. Ciò si tradurrebbe in un risultato diverso rispetto al join di sinistra se la lista reale avesse più elementi (anni che non sono presenti nella lista dei falsi). L'unione di sinistra non restituirebbe quei risultati che potrebbero essere un risultato desiderato (non chiaro da OP).

public class MyClass 
{ 
    public int ID {get; set;} 
    public int Year {get; set;} 
    public string X {get; set;} 
} 

public class MyClassEqualityComparer : IEqualityComparer<MyClass> 
{ 
    public bool Equals(MyClass x, MyClass y) 
    { 
     return x.Year == y.Year; 
    } 

    public int GetHashCode(MyClass obj) 
    { 
     return obj.ToString().ToLower().GetHashCode(); 
    } 
} 

void Main() 
{ 
    var fake = new List<MyClass> { 
      new MyClass { ID = 1, Year = 2011, X = "" } 
     , new MyClass { ID = 2, Year = 2012, X = "" } 
     , new MyClass { ID = 3, Year = 2013, X = "" } 
    }; 

    var real = new List<MyClass> { 
      new MyClass { ID = 35, Year = 2011, X = "Information" } 
     , new MyClass { ID = 77, Year = 2013, X = "Important" } 
    }; 

    var merged = real.Union(fake, new MyClassEqualityComparer()); 
} 
Problemi correlati