2012-10-20 10 views
7

Próbuję odjąć 2 listy takie jak poniższy kod, assignUsers ma 3 rekordy, a assignedUsers ma 2 wiersze. Po Except metody I jeszcze dostać 3 wiersze, chociaż powinienem dostać 1 rekord, bo 2 rzędy w assignedUsers jest podobna do assignUsersList.Except nie działa

var users = accountApp.GetUsersByAccountId(context.GetUserData().AccountId); 
List<AssignUserViewModel> assignUsers = Mapper.Map<List<AssignUserViewModel>>(users).ToList(); 
var mailUsers = mailApp.GetMailAssignedByMailId(id).Select(m => new { m.UserId, m.User.Name }).ToList(); 
List<AssignUserViewModel> assignedUsers = mailUsers.Select(Mapper.DynamicMap<AssignUserViewModel>).ToList(); 
assignUsers = assignUsers.Except(assignedUsers).ToList(); 
+2

Twój odwzorowujący jest prawdopodobnie tracąc referencje i typ prawdopodobnie nie ma innego porównywarka zdefiniowany. – leppie

+0

Napisz funkcję porównania przy użyciu programu IComparerer. –

+0

@leppie powinieneś zamieścić go jako odpowiedź :) –

Odpowiedz

24

Aby uczynić Except metoda działa zgodnie z oczekiwaniami, klasa AssignUserViewModel musi mieć GetHashCode i Equals metod poprawnie zastąpione.

Na przykład, jeśli AssignUserViewModel obiekty są jednoznacznie zdefiniowane przez ich Id, należy zdefiniować klasę w ten sposób:

class AssignUserViewModel 
{ 
    // other methods... 


    public override int GetHashCode() 
    { 
     return this.Id.GetHashCode(); 
    } 
    public override bool Equals(object obj) 
    { 
     if (!(obj is AssignUserViewModel)) 
      throw new ArgumentException("obj is not an AssignUserViewModel"); 
     var usr = obj as AssignUserViewModel; 
     if (usr == null) 
      return false; 
     return this.Id.Equals(usr.Id); 
    } 
} 

przeciwnym razie, jeśli nie może/nie chce zmienić implementację klasy , możesz zaimplementować IEqualityComparer<> i przekazać go do metody Except, np. :

class AssignUserViewModelEqualityComparer : IEqualityComparer<AssignUserViewModel> 
{ 
    public bool Equals(AssignUserViewModel x, AssignUserViewModel y) 
    { 
     if (object.ReferenceEquals(x, y)) 
      return true; 
     if(x == null || y == null) 
      return false; 
     return x.Id.Equals(y.Id); 
    } 

    public int GetHashCode(AssignUserViewModel obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
} 

wtedy Twoja ostatnia linia staną:

assignUsers = assignUsers.Except(assignedUsers, new AssignUserViewModelEqualityComparer()).ToList(); 
+1

Edytowane. Nieznacznie zmieniono porównywalność równości, tak aby uwzględniała wartości zerowe. – digEmAll

2

Dlaczego tak się dzieje? Podczas korzystania z Set Operations (Distinct, Except, Intersect, Union) Linq należy porównać elementy sekwencji pod względem równości. Domyślnie Linq używa Object.Equals i Object.GetHashCode metod porównywania elementów. Jeśli te metody nie zostaną nadpisane w twoim typie, zastosowana zostanie implementacja klasy bazowej, która porówna obiekty według wartości odniesienia. Domyślna implementacja gwarantuje, że dwa obiekty o tym samym numerze referencyjnym mają ten sam kod skrótu (co oznacza, że ​​jest równy). To jest twój przypadek. Mapper klasa tworzy nowe instancje obiektów, które mają różne odniesienia i nie mogą być traktowane jako równe (nawet jeśli wszystkie wartości pól są takie same).

Co możemy z tym zrobić?

  • Zastąp Equals i GetHashCode metody w swojej klasie. Od Ciebie zależy, jak traktujesz obiekty równości - wszystkie pola, czy tylko tożsamość. Linq użyje twoich metod do porównywania elementów.

  • Podaj swoje własne Comparer (zazwyczaj jest to przypadek, gdy nie można zmodyfikować obiekt i zastąpić Equals i GetHashCode Tak, wszystko ustawione Operations Linq dwa przeciążeń. - jeden, który używa domyślnego porównywarka i inne, które akceptuje swoje IEqualityComparer<T>.

  • Korzystanie anonimowe typy. Wszystkie typy anonimowe już wygenerowane metody Equals i GetHashCode, które korzystają z porównania wszystkich właściwości, aby określić, czy obiekty są równe. W tym przypadku nie trzeba ani modyfikować typ ani tworzyć comparer

Zatem masz już próbki pierwszych dwóch podejściach, oto ostatnia:

var assignUsers = accountApp.GetUsersByAccountId(context.GetUserData().AccountId) 
          .Select(u => new { u.UserId, u.Name }); 

var assignedUsers = mailApp.GetMailAssignedByMailId(id) 
          .Select(m => new { m.UserId, m.User.Name }); 

var assignUsers = assignUser.Except(assignedUsers); 
// do not map until here 
List<AssignUserViewModel> result = 
      assignUsers.Select(Mapper.DynamicMap<AssignUserViewModel>).ToList();