Mapping a Domain Model to the database with EF Fluent API

1.8k views Asked by At

Below is the Project code-first class mapped directly to the database through the Entity Framework 6 Fluent API:

public class Project
{
    public Project()
    {}

    public int ProjectId { get; set; }

    public string Name { get; set; }

    public bool IsActive { get; set; }

    public ICollection<ProjectVersion> ProjectVersions { get; set; }
}

Anemic models in Domain-Driven Design are an anti-pattern. I want to use this same class in my domain model instead of creating a separate Project domain class and having to perform complicated mapping between the two in the repository (and with the hundreds of other models we have).

This is how Project would look as a domain model class:

public class Project
{
    private readonly List<ProjectVersion> projectVersions;

    public Project(string name, string description)
    {
        Name = name;
        Description = description;
        projectVersions = new List<ProjectVersion>();
    }

    public int ProjectId { get; private set; }

    public string Name { get; set; }

    public bool IsActive { get; private set; }

    public IEnumerable<ProjectVersion> ProjectVersions 
    { 
        get
        {
            return projectVersions;
        }
    }

    public void AddVersion(ProjectVersion version)
    {
        projectVersions.Add(version);
    }
}

From what I have read, I am able to map to a private fields with EF's Fluent API.

Are there any any shortcomings here? Am I taking an unnecessary shortcut?

The only problem I can forsee is when a business domain model would essentially consist of data from two or more data entities.

2

There are 2 answers

1
Chris McKenzie On BEST ANSWER

I think you're making a mistake in this approach. I think you should separate the concerns of your Domain models from the concerns of your Entity models. Uncle Bob wrote a strange, but on-point blog post about this here: Dance You Imps! (seriously, it's a weird post.) The ORM's job is to act as a contract to your database. Your domain models' job is to provide the functionality. In short, you should let Entity Framework function the way it wants to. If you want to do DDD, write a mapping layer to convert EF models to your Domain models.

0
Gert Arnold On

Are there any any shortcomings here?

Possibly.

It is true that EF can address private members, so it is able to materialize a Project with a loaded ProjectVersions collection if you want. It won't use the AddVersion method for that (it doesn't even know it exists), but it will add objects to the projectVersions member.

In the application code you want to add versions through a method. There may be some problems with this AddVersion method though.

  • You can always add a ProjectVersion, but you will never be sure whether it will be stored, because for EF to track the addition projectVersions must have been loaded. However, you don't want a domain entity to be responsible for loading its own children from the database. So AddVersion gives the class a responsibility it can't fulfil to the full.
  • Calling AddVersion can occur any moment during the lifespan if the object. Usually this will be longer than the lifespan of the context by which it was created and tracked. So you can't rely on lazy loading to come to the rescue if the collection is not loaded yet. (ProjectVersions should virtual ICollection for that, by the way).
  • The conclusion is that you always have to load projectVersions eagerly (through Include) for AddVersion to be guaranteed to work properly. So there is a dependency in your application between two not obviously related pieces of code, which is a potential source of bugs.
  • When it is time to save the Project, you have to attach it to a context and find out which ProjectVersion should be marked for insert and which for update (and there's not even a RemoveVersion method yet.

All in all, it is much simpler to add versions in a service method that does all required actions within the lifecycle of a context. An added version will be marked for insert automatically. Likewise, any updated and deleted version will be marked correctly.