I came across your question in search for a solution to the same question. Here is a solution that I am trying out, see if it meets your needs:
First, all my POCOs derive from this abstract class:
public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
private readonly Guid _guid = Guid.NewGuid();
#region IEquatable<T> Members
public abstract bool Equals(T other);
#endregion
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != typeof (T))
{
return false;
}
return Equals((T)obj);
}
public override int GetHashCode()
{
return _guid.GetHashCode();
}
}
I created a readonly Guid field that I am using in the GetHashCode() override. This will ensure that were I to put the derived POCO into a Dictionary or something else that uses the hash, I would not orphan it if I called a .SaveChanges() in the interim and the ID field was updated by the base class This is the one part I'm not sure is completely correct, or if it is any better than just Base.GetHashCode()?. I abstracted the Equals(T other) method to ensure the implementing classes had to implement it in some meaningful way, most likely with the ID field. I put the Equals(object obj) override in this base class because it would probably be the same for all the derived classes too.
This would be an implementation of the abstract class:
public class Species : BasePOCO<Species>
{
public int ID { get; set; }
public string LegacyCode { get; set; }
public string Name { get; set; }
public override bool Equals(Species other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ID != 0 &&
ID == other.ID &&
LegacyCode == other.LegacyCode &&
Name == other.Name;
}
}
The ID property is set as the primary key in the Database and EF knows that. ID is 0 on a newly created objects, then gets set to a unique positive integer on .SaveChanges(). So in the overridden Equals(Species other) method, null objects are obviously not equal, same references obviously are, then we only need to check if the ID == 0. If it is, we will say that two objects of the same type that both have IDs of 0 are not equal. Otherwise, we will say they are equal if their properties are all the same.
I think this covers all the relevant situations, but please chime in if I am incorrect. Hope this helps.
=== Edit 1
I was thinking my GetHashCode() wasn't right, and I looked at this https://stackoverflow.com/a/371348/213169 answer regarding the subject. The implementation above would violate the constraint that objects returning Equals() == true must have the same hashcode.
Here is my second stab at it:
public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
#region IEquatable<T> Members
public abstract bool Equals(T other);
#endregion
public abstract override bool Equals(object obj);
public abstract override int GetHashCode();
}
And the implementation:
public class Species : BasePOCO<Species>
{
public int ID { get; set; }
public string LegacyCode { get; set; }
public string Name { get; set; }
public override bool Equals(Species other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ID != 0 &&
ID == other.ID &&
LegacyCode == other.LegacyCode &&
Name == other.Name;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return Equals(obj as Species);
}
public override int GetHashCode()
{
unchecked
{
return ((LegacyCode != null ? LegacyCode.GetHashCode() : 0) * 397) ^
(Name != null ? Name.GetHashCode() : 0);
}
}
public static bool operator ==(Species left, Species right)
{
return Equals(left, right);
}
public static bool operator !=(Species left, Species right)
{
return !Equals(left, right);
}
}
So I got rid of the Guid in the base class and moved GetHashCode to the implementation. I used Resharper's implementation of GetHashCode with all the properties except ID, since ID could change (don't want orphans). This will meet the constraint on equality in the linked answer above.