1

I have scenario to check

1) if the any prop (EmployeeObject), from empDb appear in empXml , return true. Else return false

public class EmployeeObject
{
    public Int32 Id { get; set; }
    public string Title { get; set; }
    public string Desc { get; set; }
    .....
}

IList<EmployeeObject> empDb  = PopulateFromDb(); //calling ado.net
IList<EmployeeObject> empXml = PopulateFromXml(); //deserializing xml

So let me get this straight, I'm trying to determine if list empXml is a subset of list empDb ?

Tried so far; but it returns false even thought i have check the data in both list and it should have return true unless i am doing something wrong in my expression.

//at least one MATCH
empDb.Any(a => empXml.Contains(a));

or

//at least one EXACT match
empDb.Any(x => empXml.Contains(y => x.Equals(y)));
Magnus
  • 45,362
  • 8
  • 80
  • 118
Nick Kahn
  • 19,652
  • 91
  • 275
  • 406
  • 1
    Do you have `Equals` and `GetHashCode` implemented in `EmployeeObject` class? If no, then what if you will have two objects with same id, but different title? – Sergey Berezovskiy Feb 04 '14 at 15:22

2 Answers2

3

If you don't have Equals and GetHashCode implemented in EmployeeObject class then employees will be compared by reference. And you will definitely have different instances here, because first list is created when you read data from database, and second list is created when you are deserializing xml. So, even employees with same values of all fields will be considered different.

If you want to check matches only by employee Id, then you can project sequences to ids and then use Intersect to check if match exist

// at least one employee with equal Id
empDb.Select(e => e.Id).Intersect(empXml.Select(e => e.Id)).Any()

If you want to compare employees by value of their fields instead of their references, you have several options. If you can't or don't want to change implementation of EmployeeObject class and override its Equals and GetHashCode methods, then you can create custom comparer for employees:

public class EmployeeComparer : IEqualityComparer<EmployeeObject>
{
    public bool Equals(EmployeeObject x, EmployeeObject y)
    {
        return x.Id == y.Id
            && x.Title == y.Title
            && x.Desc == y.Desc;
    }

    public int GetHashCode(EmployeeObject obj)
    {
        int code = 19;
        code = code * 23 + obj.Id.GetHashCode();
        code = code * 23 + obj.Title.GetHashCode();
        code = code * 23 + obj.Desc.GetHashCode();
        return code;
    }
}

Then you can use this comparer:

empDb.Intersect(empXml, new EmployeeComparer()).Any()

Or you can project your employees to anonymous objects (which have default implementation of Equals and GetHashCode):

empDb.Select(e => new { e.Id, e.Title, e.Desc })
     .Intersect(empXml.Select(e => new { e.Id, e.Title, e.Desc })).Any()

Or override these methods:

public class EmployeeObject
{
    public Int32 Id { get; set; }
    public string Title { get; set; }
    public string Desc { get; set; }

    public override int GetHashCode()
    {
        int code = 19;
        code = code * 23 + Id.GetHashCode();
        code = code * 23 + Title.GetHashCode();
        code = code * 23 + Desc.GetHashCode();
        return code;
    }

    public override bool Equals(object obj)
    {
        EmployeeObject other = obj as EmployeeObject;
        if (other == null)
            return false;

        if (ReferenceEquals(this, other))
            return true;

        return Id == other.Id && 
               Title == other.Title && Desc == other.Desc;                 
    }
}

And your code will work. Or you can use Intersect:

empDb.Intersect(empXml).Any()
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • What if; if i have to compare with `Title or Desc or Id` is there a way to create a one method instead of creating three? and also what is wrong with my code that i have in my question why would that not work? – Nick Kahn Feb 04 '14 at 15:30
  • @AbuHamzah I already have explained in first paragraph why your code not works. If you want to compare several properties, then either override `Equals` and `GetHashCode` of your class or create custom comparer and pass it to `Intersect` – Sergey Berezovskiy Feb 04 '14 at 15:32
  • okay i got it; i have to compare with Title or Desc or Id is there a way to create a one method instead of creating three? the one way i can do is to add `if else statement` but is there any other better way to handle. – Nick Kahn Feb 04 '14 at 15:35
  • 1
    what is the code 19 and 23 is that random number you pick? – Nick Kahn Feb 04 '14 at 16:48
  • @AbuHamzah that is prime numbers - one of approaches to generate pretty unique hash, see [What is the best algorithm for an overridden System.Object.GetHashCode?](http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode) – Sergey Berezovskiy Feb 04 '14 at 16:51
2

If Id is the primary key of the entity you might want to write:

var set = new HashSet<int>(empXml.Select(x => x.Id)); //For faster lookup
empDb.Any(a => set.Contains(a.Id));

But if you need to match on all properties you need to override Equals and GetHashCode.
(this implementation also match on null values for the properties)

public class EmployeeObject : IEquatable<EmployeeObject>
{
        public bool Equals(EmployeeObject other)
        {
            return Id == other.Id && 
                   string.Equals(Title, other.Title) && 
                   string.Equals(Desc, other.Desc);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((EmployeeObject) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = Id;
                hashCode = (hashCode*397) ^ (Title != null ? Title.GetHashCode() : 0);
                hashCode = (hashCode*397) ^ (Desc != null ? Desc.GetHashCode() : 0);
                return hashCode;
            }
        }

        public Int32 Id { get; set; }
        public string Title { get; set; }
        public string Desc { get; set; }
    }

And the write:

var set = new HashSet<EmployeeObject>(empXml); //For faster lookup
empDb.Any(a => set.Contains(a));
Magnus
  • 45,362
  • 8
  • 80
  • 118