Skip to main content
  1. Posts/

Improved Support for Value Objects with EF Core 2.2

·581 words·3 mins

Improved Support for Value Objects with EF Core 2.2

Entity Framework Core 2.2 brings long-awaited improvements for supporting value objects. Value objects are the building blocks of a robust domain model but until now it was difficult to map them to your database using EF Core. I have prepared a sample project to illustrate how we can leverage the latest update to better support values objects.

An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object

— Eric Evans

If you are just learning about value objects, they are a fundamental concept in Domain-Driven design and I can recommend the following resources to get you started on this concept and its importance in domain models - one, two, three.

Up until now, there wasn’t a straightforward way to map a collection of value objects to the database using Entity Framework Core. Image that we have a domain concept of a company and that a company can have a number of company addresses. The company address entity is totally dependent on the company and it does not make sense on its own. This is a perfect scenario to leverage value objects. Let’s take a look at our simple domain:

public class CompanyAddress : ValueObject
{
    public CompanyAddress(string city, string addressLine1)
    {
        Assertions.AssertNotNullAndNotEmpty(city, "Must provide city");
        Assertions.AssertNotNullAndNotEmpty(addressLine1, "Must provide address line");

        this.City = city;
        this.AddressLine1 = addressLine1;
    }

    public string City { get; }

    public string AddressLine1 { get; }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return this.City;
        yield return this.AddressLine1;
    
    }
}
public class Company
{
    private List<CompanyAddress> addresses = new List<CompanyAddress>();

    public Company(Guid id, string name)
    {
        Assertions.AssertNotNullAndNotEmpty(name, "Must provide name");
        this.Id = id;
        this.Name = name;
    }

    public Guid Id { get; }
    
    public string Name { get; }

    public IEnumerable<CompanyAddress> Addresses
    {
        get
        {
            return this.addresses;
        }
    }

    public void AssignAddress(CompanyAddress address)
    {
        Assertions.AssertNotNull(address, "Must provide address");

        var exists = this.addresses.Contains(address);

        if (!exists)
        {
            this.addresses.Add(address);
        }
    }
}

So this is our simple domain. One of the most important things to look our for when using ORMs like Entity Framework is to protect your domain from corruption. For example, making all of your properties settable, adding unnecessary ID fields to your entities, etc. simply because the ORM needs them.

The good news is the past several EF Core updates have introduced features that allow us to map our domain models to the DB without sacrificing its integrity. The 2.2 update now makes it possible to map Value Objects like the CompanyAddress with a feature called Collections of owned types.

To properly map CompanyAddress we use the OwnsMany when configuring the DB model.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Company>().OwnsMany<CompanyAddress>("Addresses", a =>
    {
        a.HasForeignKey("CompanyId");
        a.Property(ca => ca.City);
        a.Property(ca => ca.AddressLine1);
        a.HasKey("CompanyId", "City", "AddressLine1");
    });
}

We create a foreign key, list all properties of the value object, and finally its key. Now we are ready to work with our domain objects:

using (var context = new CompanyContext())
{
    var company = new Company(Guid.NewGuid(), "My Company");
    company.AssignAddress(new CompanyAddress("Sofia", "Mladost 4"));
    company.AssignAddress(new CompanyAddress("Plovdiv", "blvd. Bulgaria 105"));
    context.Companies.Add(company);
    context.SaveChanges();
}

I have prepared a fully functional project which is available on Github in case you want to take a look.

As always, I am looking forward to your questions or comments. Thank you for stopping by.