Domain-Driven Design - Entities in C#

Before you look at the Entities example in C#, you can see at my post on what Entities are in Domain-Driven Design.

Entity interface in C#

public interface IEntity
{
  Guid Id { get; }
}

About this code snippet:

  • It have an Id property without a set accessor, so this property could only be populated within the constructor of the class that implements it.

Entity base class in C#

public abstract class Entity : IEntity
{
  public Guid Id { get; }

  protected Entity() { }
  protected Entity(Guid id)
  {
    this.Id = id;
  }

  public bool Equals(Entity other)
  {
    if (other is null) return false;

    if (this.GetType() != other.GetType()) return false;

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

    return this.Id.Equals(other.Id);
  }

  public override bool Equals(object obj) => this.Equals(obj as Entity);

  public static bool operator ==(Entity a, Entity b) => a.Equals(b);

  public static bool operator !=(Entity a, Entity b) => !a.Equals(b);

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

About this code snippet:

  • It is an abstract class: This prevents this class from being instantiated and allows to declare abstract methods.
  • It have a public Id property without a set accessor, so this property can only be populated within the constructor.
  • It have an Equals method to verify equality with other entities by checking its Id property.

Entity example in C#

public sealed class User : Entity
{
  public FirstName FirstName { get; private set; }

  private User() { }
  private User(Guid id, FirstName firstName) : base(id)
  {
    this.FirstName = firstName;
  }

  public static User Create(Guid id, FirstName firstName)
  {
    return new User(id, firstName);
  }

  public void UpdateFirstName(FirstName firstName)
  {
    if (this.FirstName.Equals(firstName))
      throw new InvalidFirstNameException();

    this.FirstName = firstName;
  }
}

About this code snippet:

  • It is a sealed class: Prevent another class from extends from this one.
  • Their properties are private set, so they must be modified inside the same class instance.
  • No public constructor: Other classes cannot create instances of this class, except for nested classes.
  • To create instances of this Value Object we do not call the constructor directly, but it uses the Static Factory Method pattern instead.
  • It have methods to change the value of the entity’s properties.
  • If an attempt is made to update a property using the same value, it throws an exception.