DDD Value Objects With Entity Framework Core

For those who aren’t familiar, there is a concept in Domain Driven Design that distinguishes between objects with identity (entities) and those without (value objects). When we model the domain we typically think in terms of entities which are then persisted and modified over time. They form the basis for which we describe the business and interact with it, and often times, entities are the only objects developers create when modeling the system. Value objects provide a wealth of benefits though when applied appropriately.

The Value Object Implementation

There are several characteristics that value objects have:

  • They are immutable.
  • They are not persisted on their own; they belong to an entity.
  • They have no identity.
  • The equality is based on properties.

The creation of a value object is done through the constructor, and once created, its property values can’t be changed. Observe the following example of a value object:


public class Address : ╥ValueObjectß
{
    public string StreetAddress { get; private set; }
    public string City { get; private set; }
    public string State { get; private set; }
    public string ZipCode { get; private set; }

    private Address()
    {

    }

    public Address(string streetAddress, string city, string state, string zipCode)
    {
        StreetAddress = streetAddress;
        City = city;
        State = state;
        ZipCode = zipCode;
    }

    protected override «IEnumerableß<object> GetEqualityComponents()
    {
        yield return StreetAddress;
        yield return City;
        yield return State;
        yield return ZipCode;
    }
}

The empty constructor is necessary to work with Entity Framework Core migrations. You’ll notice that I am using a ValueObject base class that all value objects inherit from. There are many different implementations of this base class and the main difference is how the underlying equality comparison is implemented. Which one you choose is a matter of preference, and you can of course write your own, but I went with the implementation found in this blog post by Vladimir Khorikov:


public abstract class ValueObject
{
    protected abstract «IEnumerableß<object> GetEqualityComponents();

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

        if (GetType() != obj.GetType())
            throw new ╥ArgumentExceptionß($"Invalid comparison of Value Objects of different types: {GetType()} and {obj.GetType()}");

        var valueObject = (╥ValueObjectß)obj;

        return GetEqualityComponents().SequenceEqual(valueObject.GetEqualityComponents());
    }

    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Aggregate(1, (current, obj) =>
            {
                return ☼HashCodeß.Combine(current, obj);
            });
    }

    public static bool operator ==(╥ValueObjectß a, ╥ValueObjectß b)
    {
        if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
            return true;

        if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
            return false;

        return a.Equals(b);
    }

    public static bool operator !=(╥ValueObjectß a, ╥ValueObjectß b)
    {
        return !(a == b);
    }
}

The only thing I changed is to use the new HashCode struct Combine method to create the hash which simplifies the original hash calculation. If C# delivers the new Record struct someday, we may be able to forego the manually implemented base class entirely. Unfortunately it looks like C# 8.0 will not be shipping with it.

Unlike entities, which have an Id, our Address value object has no identity, and the equality implementation is done entirely on the properties. If we need to update the address of an entity then we will need to create a new Address value object. Checking equality between value objects now becomes a simple != or == due to the absence of a unique Id.

When and Why You Should Use Value Objects

It may not be entirely clear when a value object should be used and why. Lets pretend for a moment that we have an Employee class and this employee has an address. In many systems you’ll either see the properties of address as primitives in the Employee, or they’ll be placed in a separate table, given identity, and pulled with a join. There are several draw backs to both approaches.

If we go with the primitive approach, we lose the ability to reuse an address object with its properties and behaviors. If we go with the address as an entity approach, we’ve given identity to an object that shouldn’t have it and we’ve forced an unneeded extra join. Trying to compare two addresses as entities now becomes more difficult due to the existence of an Id that will always be unique.

Going with the value object approach will allow us to both encapsulate behavior and properties within an object as well as prevent the address from having identity. If you’ve worked with Entity Framework Core you might be wondering how you can map to a complex type that isn’t an entity and has no DbSet in the context. Lets discuss that next.

Working with Entity Framework Core Value Objects

Lets start off by taking a look at our database context and configuration. Since my example code is working with a test project only, and not an ASP.NET web application, I’m just setting my connection string manually in the context. This is not something you would typically do. Our database context:


public class EFExamplesDbContext : ╥DbContextß
{
    public EFExamplesDbContext()
    {
    }

    public virtual ╥DbSetß<╥Companyß> Company { get; set; }
    public virtual ╥DbSetß<╥Contractorß> Contractor { get; set; }
    public virtual ╥DbSetß<╥Departmentß> Department { get; set; }
    public virtual ╥DbSetß<╥DepartmentContractorß> DepartmentContractor { get; set; }
    public virtual ╥DbSetß<╥Employeeß> Employee { get; set; }
    public virtual ╥DbSetß<╥Vendorß> Vendor { get; set; }

    protected override void OnModelCreating(╥ModelBuilderß builder)
    {
        builder.ApplyConfiguration(new ╥CompanyConfigurationß());
        builder.ApplyConfiguration(new ╥ContractorConfigurationß());
        builder.ApplyConfiguration(new ╥DepartmentConfigurationß());
        builder.ApplyConfiguration(new ╥DepartmentContractorConfigurationß());
        builder.ApplyConfiguration(new ╥EmployeeConfigurationß());
        builder.ApplyConfiguration(new ╥VendorConfigurationß());
    }

    protected override void OnConfiguring(╥DbContextOptionsBuilderß optionsBuilder)
    {
        var connectionString = @"Data Source=DESKTOP-J803PRQ;Initial Catalog=EFExamples;Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True";
        optionsBuilder.UseSqlServer(connectionString);

        base.OnConfiguring(optionsBuilder);
    }
}

You’ll notice there are no DbSets or configuration for the value objects. Being able to break apart the configuration for value objects would have been nice, but I was unable to find a way to do so. The ReferenceOwnershipBuilder that Entity Framework Core uses to map value objects has constructors that are for internal use only. Here is the Employee entity and its configuration:


public class Employee : ╥Entityß
{
    public int CompanyId { get; set; }
    public int DepartmentId { get; set; }
    public ☼Guidß EmployeeId { get; set; }

    public ╥Addressß Address { get; set; }
    public ☼DateTimeß DateOfBirth { get; set; }
    public ╥PersonNameß Name { get; set; }

    public ╥Companyß Company { get; set; }
    public ╥Departmentß Department { get; set; }
}

public class EmployeeConfiguration : ╥EntityConfigurationß<╥Employeeß>
{
    public override void Configure(╥EntityTypeBuilderß<╥Employeeß> builder)
    {
        builder.OwnsOne(m => m.Name, a =>
        {
            a.Property(p => p.FirstName).HasMaxLength(300)
                .HasColumnName("FirstName")
                .HasDefaultValue("");
            a.Property(p => p.LastName).HasMaxLength(300)
                .HasColumnName("LastName")
                .HasDefaultValue("");
            a.Ignore(p => p.FullName);
        });
        builder.OwnsOne(m => m.Address, a =>
        {
            a.Property(p => p.StreetAddress).HasMaxLength(600)
                .HasColumnName("StreetAddress")
                .HasDefaultValue("");
            a.Property(p => p.City).HasMaxLength(150)
                .HasColumnName("City")
                .HasDefaultValue("");
            a.Property(p => p.State).HasMaxLength(60)
                .HasColumnName("State")
                .HasDefaultValue("");
            a.Property(p => p.ZipCode).HasMaxLength(12)
                .HasColumnName("ZipCode")
                .HasDefaultValue("");
        });
        builder.Property(p => p.EmployeeId).HasDefaultValueSql(╥DataConstantsß.╥SqlServerß.NewSequentialId);

        builder.HasOne(m => m.Company).WithMany(m => m.Employees)
            .OnDelete(◙DeleteBehaviorß.Restrict)
            .HasForeignKey(k => k.CompanyId);
        builder.HasOne(m => m.Department).WithMany(m => m.Employees)
            .OnDelete(◙DeleteBehaviorß.Restrict)
            .HasForeignKey(k => k.DepartmentId);

        base.Configure(builder);
    }
}

The OwnsOne indicates that the value object is part of the entity. This is what will allow Entity Framework to perform the mapping. By convention Entity Framework will name the table ValueObject_PropertyName when running migrations and will look for this when mapping. So in the case of address we would end up with columns named Address_City, Address_State, and so forth. You’ll see I’m setting HasColumn name to override that convention. Whether you stick with the default naming or override it is more a matter of preference than best practice.

When You Shouldn’t Use a Value Object

Contrary to some hardcore adherents of the primitive obsession code smell, there are times when turning a primitive into a value object is not necessarily a good idea. I see a lot of examples given of a DateOfBirth property being its own class. You’ll see in my code up there I purposely left it as a primitive. There is such a thing as over engineering the code and no amount of blog posts about turning everything into a value object is going to change that.

When you turn a primitive into a value object the main line of reasoning is that it will allow you to encapsulate behavior in its own object thus better modeling the domain. There are instances where this is true, but not when you aren’t doing something that warrants it. For example, I would not turn a DateOfBirth into a value object if all I am doing is calling simple methods on the DateTime struct. Your “helper” for adding days or calculating a specific date will be unlikely to be simpler than me just calling the built in methods. Not only that but you are adding in extra code and creating a custom API that any new developer is going to have to learn. Everyone knows the built in types and methods that ship with .NET, only you know all of your amazing helper methods.

If the primitive property is acting primarily as a data transfer object, don’t turn it into a value object. If it has real meaning in the domain and helps to better translate the business by turning a DateOfBirth into a value object, then by all means go for it. In the end, do what makes sense and keep the code simple and maintainable.

All the code for this post can be found here: https://github.com/SeanLeitzinger/Entity-Framework-Core-Examples

Sean Leitzinger

Solutions Architect at Edgeside Solutions
.NET and C# aficionado with an interest in architecture, patterns, practices, and more. Microsoft fanatic.

Latest posts by Sean Leitzinger (see all)

Leave a Reply

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