Entity Base Class

If you follow DDD principles, you eventually end up creating a base class for all the domain entities. It’s a good idea as it allows you to gather common logic in one place. When you decide to do that, you inevitably face the question of what exactly should be included in that base entity and how it should be presented.

Interface as a base entity

I often see developers using interfaces as a base entity. The code might look like this:

public interface IEntity
{
    int Id { get; }
}

While this approach guarantees that all domain entities have some minimum functionality (Id property in this example), in most cases having an interface as a base entity is a bad idea.

First of all, interfaces don’t allow you to keep any logic in them, and you’ll need to implement the same logic in all the inheriting classes, which leads to code duplication and violation of the DRY principle. Even with the simple example above, you’ll have to introduce the Id property in every single entity.

Secondly, using an interface doesn’t show the appropriate relationship between domain entities. When a class implements an interface it makes a promise about some functionality, but that’s not enough. Two classes implementing the same interface don’t show how they are related to each other; they may belong to entirely unconnected hierarchies. In other words, the IEntity interface introduces a “can do” relation (according to Bertrand Meyer’s classification), whereas domain entities should be connected to the base entity by an “is a” relation. Every domain class not only has the Id property, but itself is an entity. It is important to remove possible misunderstandings; using an interface instead of the base class can lead to one.

How you shouldn’t implement base classes

Okay, but what logic do we need in the base domain class?

Obviously, it should have an Id field, which is mapped to a table’s primary key. All tables in the database must have ids with the same type so we could factor the Id property out to the base class. Here is how you should not do it (at least not from the very beginning):

public class Entity<T>
{
    public T Id { get; protected set; }
}

Motivation for such code it pretty clear: you have a base class that can be reused across multiple projects. For instance, if there is a web application with GUID Id columns in the database and a desktop app with integer Ids, it might seem like a good idea to have the same class for both of them.

But this approach introduces accidental complexity because of premature generalization. There is no need for a single base entity class when you work with multiple projects or bounded contexts. Each domain has its unique path, so let it grow independently. Just copy and paste the base entity class to a new project and specify the exact type that will be used for the Id property. Only when the need for entities with different Id types arises in the single project/bounded context, should you introduce a type parameter in the Entity base class.

Entity base class: the code

So, what should the base domain class look like? Here is the code I use in production, let’s step through it.

public abstract class Entity
{
    public virtual long Id { get; protected set; }

    protected Entity()
    {
    }

    protected Entity(long id)
    {
        Id = id;
    }

    public override bool Equals(object obj)
    {
        if (obj is not Entity other)
            return false;

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

        if (GetUnproxiedType(this) != GetUnproxiedType(other))
            return false;

        if (Id.Equals(default) || other.Id.Equals(default))
            return false;

        return Id.Equals(other.Id);
    }

    public static bool operator ==(Entity a, Entity b)
    {
        if (a is null && b is null)
            return true;

        if (a is null || b is null)
            return false;

        return a.Equals(b);
    }

    public static bool operator !=(Entity a, Entity b)
    {
        return !(a == b);
    }

    public override int GetHashCode()
    {
        return (GetUnproxiedType(this).ToString() + Id).GetHashCode();
    }

    internal static Type GetUnproxiedType(object obj)
    {
        const string EFCoreProxyPrefix = "Castle.Proxies.";
        const string NHibernateProxyPostfix = "Proxy";

        Type type = obj.GetType();
        string typeString = type.ToString();

        if (typeString.Contains(EFCoreProxyPrefix) || typeString.EndsWith(NHibernateProxyPostfix))
            return type.BaseType;

        return type;
    }
}

By default, the Id property is of long type, which excludes the possibility of sequence exhausting (the long type may contain numbers up to 9,223,372,036,854,775,807). Also, all members are made virtual in case you are using NHibernate where virtual members are required to create runtime proxies. Setter is made protected and not private because of NHibernate as well.

The most interesting part is equality members. There are three types of equality in enterprise software:

  • Reference equality means that two references refer to the same object in memory.

  • Identifier equality means that two objects in memory refer to the same row in the database.

  • Structural equality means that two objects deemed the same when they have the same structure. This is useful happen when an object doesn’t have its own identity (such objects are called value objects).

The Entity class covers the first two situations: when two objects either equal by reference or by identifier. Structural equality pertains to Value Objects.

Notice the GetUnproxiedType method. It’s here to address the issue of ORMs returning the type of a runtime proxy when you call GetType() on an object. This method returns the underlying, real type of the entity. It handles both NHibernate and EF Core.

The second if statement in Equals checks reference equality and the last one checks identifier equality. The Id.Equals(default) line checks for transient entities. Such entities are not yet saved to the database and have the Id set to zero by default. We cannot check identifier equality in such entities, and hence treat two entities as different even if both of them have the Id property set to zero.

The GetHashCode method must be overridden together with the Equals method. They always come together because of the internal .NET logic. This logic can be illustrated in the code below. When you call Contains() method it loops through the objects in the list and calls GetHashCode() on each object. If the hash codes are the same, .NET also calls Equals() to make sure the objects are indeed equal to each other. Only if the both checks, GetHashCode and Equals, are passed the two objects are considered equal.

Entity obj = GetObjectSomehow();
List<Entity> objects = GetObjectsSomehow();
objects.Contains(obj);

It is a good idea to declare == and != operators as well, because by default == operator checks reference equality only.

Summary

I use this code in most of my projects, sometimes adding some functionality if it appears in all project’s domain classes. Such functionality might be version property (byte[] or int) or any other useful stuff. But don’t be carried away by this, add functionality to the base domain class if it belongs to all domain classes only. Otherwise, you may end up building God Object instead of a thin base entity.

Update 4/1/2019

The Entity base class no longer contains the GetRealType method and doesn’t rely on NHibernate anymore. Thanks to Anders Baumann for suggesting the workaround.

Update 1/25/2021

I’ve updated the base class to be compatible with both NHibernate and EF Core. You can also use this library to reference the latest version of the Entity base class.

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus