Using Linq Except to return items deleted from a list but have the same Id

Viewed 331

I have the following lists:

var previousRun = new List<Data>();
var currentRun = new List<Data>();

var item1= new Data() { Id = "1", Address = "Test Address", Name = "Test", PostCode = "ABC DEF" };
var item2 = new Data() { Id = "2", Address = "Test Address2", Name = "Test2", PostCode = "GHI JKL" };

var item1Updated = new Data() { Id = "1", Address = "UpdatedAddress", Name = "UpdatedTest", PostCode = "STU VWX" };
var item2Updated= new Data() { Id = "2", Address = "UpdatedAddress2", Name = "Test2", PostCode = "TES TIN" };

previousRun.Add(item1);
previousRun.Add(item2);

currentRun.Add(item1updated);
currentRun.Add(item2updated);

I am trying to count the number of items that have been deleted from the previous list in the current list:

public DataChange<Data>[] Compare(Data[] previous, Data[] current)
{
    var deletedResults = previous
        .Except(current)
        .Select(DataChange<Data>.Deleted)
        .ToList();
}

In this scenario, I want the result to be 0 as nothing has been deleted, only updated (I appreciate that it could be argued that the originals have been 'deleted' but I will also have a list of updated items, so deleted really means that the IDs have been deleted.

Instead deletedResults is 2, as it believes both the original items have been removed. Can anyone please help with a Linq query that will only show items as 'deleted' if the Ids are in the previous list but not the current one.

UPDATE

I have implemented an EqualityComparer as suggested in the comments:

public class DataEqualityComparer: IEqualityComparer<GpLookupData>
    {
        public bool Equals(Data previous, Data current)
        {
            var idComparer = string.Equals(previous.Id, current.Id, System.StringComparison.OrdinalIgnoreCase); 
            var nameComparer = string.Equals(previous.Name, current.Name, System.StringComparison.OrdinalIgnoreCase);
            var addressComparer = string.Equals(previous.Address, current.Address, System.StringComparison.OrdinalIgnoreCase);
            var postcodeComparer = string.Equals(previous.PostCode, current.PostCode, System.StringComparison.OrdinalIgnoreCase); 
          

            if (idComparer && nameComparer && addressComparer && postcodeComparer) 
            {
                return true; 
            }

            return false; 
        }

        public int GetHashCode(Data obj)
        {
            return obj.Name.GetHashCode();
        }
    }
2 Answers

.Except() will use the default equality comparer if a IEqualityComparer<T> has not been specified.

The default equality comparer will use the following logic:

  1. If the type implements IEquatable<T>, use the bool Equals(T other) method
  2. If the type overrides bool Equals(object), use that.
  3. If the type is a reference type, use ReferenceEquals(object, object). If the type is a value type, each field in the type will be compared.

My guess is that the Data-type is a reference type that does not override any equals method, so reference equality will be used. So you would need to define how the objects are compared.

If you implement/override any of the equals methods, keep in mind to also override the GetHashCode() method.

deleted really means that the IDs have been deleted.

So it sounds like you want all IDs that are in list 1 but not list 2.

You don't need Except and an equality comparer for that - just do:

var deleted = previousRun.Where(p => !currentRun.Any(c => c.Id == p.Id));

If you need a little better performance you can create a hash set from the "current" IDs:

var idList = new HashSet<int>(currentRun.Select(c => C.Id));
var deleted = previousRun.Where(p => idList.Contains(p.Id));
Related