How to get ORM (Ef Core) Entities follow a DDD style . Which is the best approach

entity-frameworkrepository-patternunit-of-work

I am considering mutliple options to face a problem.
I want to develop software following DDD style.
The problem arise when I have to define my entities and I am working with Entity Framework Core, any other ORM would have the same problems.

ORM generates plain POCOs entities that represents the Tables in our DB. That don't fits DDD style.

Ways to accomplish a DDD style entities:

A) Implement Repository/Unit of Work patterns + Mapping: Use some kind of mapping process inside the repositories implementations, between the Ef entities and the Domain entities we work with. We got a set of repositories that makes a facade to EF Core, and all the EF implementation details remains decopupled.

Pros

  • Decopuling achieved
  • Centralized mapping configuration
  • Repositories return DDD style entities, easy to aggregate and follow DDD rules.

Cons

  • Loose of some EF features , autotracking => Less efficient
  • The possibility to launch Expression> queries=> Over-verbose methods in the repos to fit all needs.
  • Our implementation of UoW will be poor compared to Ef/ORM ones

B) Turn EF Core Entities into DDD style.: ORM usually allow us to define our classes before and then map them to a database with some configuration. Following this path you could get , business logic inside entities and DDD related practices( private setters/ aggregation) .See answer of @Flater for more detail.

Pros

  • All the features implemented by specific ORM(EF Core) => efificency, auto tracking , etc
  • We don't have to implement an "overdesign" of Repository/UoW patterns

Cons

  • We lose decoupling between layers, because we introduce dependency on the DbContext which is a specific tech related.

Do you know other approaches?
Which pros and cons have them?

Best Answer

You mention "EF auto generated classes", which suggest you're using a DB-first approach. But you mentioned in the comments that you're open to anything, so I want to suggest switching to a Code First approach.

Robert Harvey's answer isn't wrong, I simply want to offer another viable solution to the problem.

Note: I don't know much about EF Core specifically, my answer uses EF but I assume that there aren't too many differences between the two.


EF has two approaches:

  • Database First - EF generates C# based on an existing database
  • Code First - EF generates a databased based on existing C# classes.

So the idea is simple: create your classes, and then tell EF that you want it to create the database accordingly. This gives you direct control over your classes and where you store them.

A basic example

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    public MyContext() : base("MyConnectionStringName")
    { 

    }

    public DbSet<User> Users { get; set; }
}

This is enough information for EF to generate a database for you. It will generate a table for every DbSet<T> you specify, and it will generate that table's columns based on the class definition of T.
EF will make some assumptions (e.g. that a property named Id is intended to be the PK of your table). You can change these default behaviors; more on that further in the answer.

There is a lot more information on this subject than I can provide in the answer. Plenty of tutorials and guides exist online (example), I suggest you look into these to learn about the more detailed configuration options that EF puts at your disposal.


However, there is one thing I still want to point out. When you want to adjust your db columns, e.g. putting a max length on a certain string property, there are two ways of doing so.

Firstly, and most commonly found online, you can use attributes on your class:

public class User
{
    public int Id { get; set; }

    [MaxLength(50)]
    public string Name { get; set; }
}

These attributes are mostly provided by EF. Some others (e.g. [NotMapped]) are not provided by EF but EF does observe and respond to them (in the case of [NotMapped], the property will not generate a table column).

As you may suspect, many attributes are available.

But there is a second way, using the Fluent API. This will initially feel more contrived, but it's actually the better option when you want to take a DDD approach. Instead of decorating your entity with attributes, you instead "register" these configurations on the context itself:

public class MyContext : DbContext
{
    public MyContext() : base("MyConnectionStringName")
    { 

    }

    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(p => p.Name).HasMaxLength(50);
    }
}

As far as I'm aware, every attribute has an equivalent Fluent API alternative.

But why use Fluent for DDD?

The reason I want to suggest this is because the attributes require you to have access to the EF dependency in your class definition, which means that your entity will be defined in the data layer.

But you don't want that. You want to have a separate domain layer where your classes are defined and then consumed by the data layer. By using the Fluent API, you can ensure that this is what happens. The MyContext class is already required to be part of the data layer (since it derives from EF's DbContext), so it makes more sense to have it also take care of the EF-specific configuration of the table columns.