Entity Classes, IDs and Equality

I want my entity classes to rely on their ID when checking equality, the ID is populated from the repository when I retrieve an object and automatically generated when I save a new entity.

This poses the problem of how I compare two entities before they have been saved while working with them in my domain, or, when I have ‘newed’ them up myself for unit testing. The answer is to use a transient ID.

Normally an Entity Base class might look like this:

public abstract class EntityBase : IEquatable<EntityBase> {

    public virtual int Id { get; protected set; }

    public override bool Equals(object other) {
        return Equals(other as EntityBase);
    }

    public override int GetHashCode() {
        return Id;
    }

    public virtual bool Equals(EntityBase other) {
        if (other == null) return false;
        return other.Id == Id && other.GetType() == GetType();
    }

}

If two new objects are created from a derived class then object1.Equals(object2) will be true as Id will be 0 for both objects. To avoid this I need to check if an object has not yet been saved:

private bool IsTransient {
    get { return Id == 0; }
}

And just check for referential equality if this is the case:

public virtual bool Equals(EntityBase other) {
    if (other == null) return false;
    if (IsTransient) return ReferenceEquals(this, other);
    return other.Id == Id && other.GetType() == GetType();
}

GetHashCode() needs to be overridden to reflect this:

public override int GetHashCode() {
    if (IsTransient) return base.GetHashCode();
    return Id;
}

The full code for EntityBase is:

public abstract class EntityBase : IEquatable<EntityBase> {

    public virtual int Id { get; protected set; }

    private bool IsTransient {
        get { return Id == 0; }
    }

    public override bool Equals(object other) {
        return Equals(other as EntityBase);
    }

    public override int GetHashCode() {
        if (IsTransient) return base.GetHashCode();
        return Id;
    }

    public virtual bool Equals(EntityBase other) {
        if (other == null) return false;
        if (IsTransient) return ReferenceEquals(this, other);
        return other.Id == Id && other.GetType() == GetType();
    }

}

3 thoughts on “Entity Classes, IDs and Equality

  1. Thanks Xhafan,
    Those are good points and there are other solutions such as caching the hashcode for the lifetime of the object. But there is a trade off, whichever solution you take. As mentioned in the links, not changing the hashcode is a guideline rather than a rule, but it is definitely worth pointing out that a persisted object != an unpersisted one.

    The actual rule is:
    Rule: the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable.

    Thanks for pointing that out.

  2. Have you noticed (resolved) an issue with a given entity’s existance in child collection after removing from its DbSet. Using your implementation of overridden Equality when removing a Child, the Child does not get removed from it’s parent collection. Without the overridden Equality EF behaves as expected.

    Example:
    public Store
    {
    public virtual ICollection Movies {get;set;}
    }

    Steps
    1. Spin up DbContext
    2. Create new Movie (Set.Create())
    3. Add new Movie to store.Movies collection
    4. DbContext.SaveChanges()
    5. DbSet.Remove(newMovie)
    6. Error – store.Movies collection still contains newMovie (without the overridden Equality newMovie gets removed from the store.Movies collection.

    Interested in your thoughts.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.