DDD
9th April 2021
by Christoph Perger

Eliminating the need of adding private setters to bind read-only properties by using IL weaving and the Fody plugin SpatialFocus.AddSetter.Fody.

About this series

When designing a clean DDD solution, many times you need to pollute your domain with infrastructure code in order to get Entity Framework (EF) working. This blog series will look at some of these issues and how to resolve them.

The issue

Many of the properties in our entities are being set via the constructor and should be purely read-only. So we could just remove the setters, but Entity Framework requires them to work properly.

If you check the EF Core documentation, it says:

Properties without setters are not mapped by convention. (Doing so tends to map properties that should not be mapped, such as computed properties.)

The documentation recommends to add private setters, or to add a more explicit mapping in OnModelCreating in the infrastructure code. The second would suit our DDD approach and keep infrastructure code separated, however it will be quite labor intensive in bigger projects.

The first option will not only make our code less self-explaining, the code will also display compiler warnings, indicating that the setters are never used.

The solution

With the help of IL Weaving we can keep stick to the first option from above but still keep the the domain model clean at design-time. The private setters will be weaved into our entities at build-time. We haven't found any existing Fody plugin, so we created one for this purpose. We named it SpatialFocus.AddSetter.Fody. The are only two steps necessary:

  • Install the SpatialFocus.AddSetter.Fody NuGet package and update the Fody NuGet package
  • Add <SpatialFocus.AddSetter/> to FodyWeavers.xml

In our case we configured the FodyWeavers.xml to include only our domain entities in the Domain.Model namespace and below:

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
    <SpatialFocus.AddSetter>
        <IncludeNamespaces>
            OurProjectTitle.Domain.Model
            OurProjectTitle.Domain.Model.*
        </IncludeNamespaces>
    </SpatialFocus.AddSetter>
</Weavers>

The result

Instead of polluting our domain entities with properties that contain unused and potentially misleading setters, we end up with a clean domain entity class and real read-only properties:

public class Car
{
    public Car(string name, Specs specs)
    {
        Name = name;
        Specs = specs;
    }

    public CarHolder? CarHolder { get; protected set; }

    public int Id { get; }

    public string Name { get; }

    public Specs Specs { get; }
}

Thanks to the Fody plugin, after the build, EF Core will find the private setters and will map them properly.

The part 4 of our series will take a closer look at some lazy-loading specifics in our properties.