Search code examples
c#entity-frameworkdomain-driven-designef-core-2.0ddd-repositories

EF Core incapsulation in Unit Of Work pattern


I've got problems with joining DDD and EF Core.

I'm making project using DDD architecture. As Data Access level I use generic Unit of Work pattern taken from here.

public interface IUnitOfWork
{
    IRepository<TDomain> Repository<TDomain>() where TDomain : class;
}

public interface IRepository<TDomain>
{
    TDomain Get(Expression<Func<TDomain, bool>> predicate);
}

Realizing these interfaces I use EF Core.

I've got some domain model with 2 classes

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

    public List<RelatedItem> Items { get; set; }
}

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

    public MainClass Parent { get; set; }

    public DateTime Date { get; set; }

    public string SomeProperty { get; set; }
}

In real life of my project MainClass has collection with hundreds of RelatedItems. In order to perform some operations I need only one RelatedItem per request with some date. It can be done by searching through Items property.

Incapsulating perfomance of EF Core in unit of work I have to load explicitly entities from DB with related items, because business login layer doesn't know anything about realization of UnitOfWork's repository. But this operation is very slow.

So I decided to create MainClassService which injects in costructor unitOfWork and have method which returns only one RelatedItem, and it works fine.

public class MainClassService
{
    IUnitOfWork unitOfWork;

    public MainClassService(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork ?? throw new ArgumentNullException();
    }

    public RelatedItem GetRelatedItemByDate(int mainClassId, DateTime date)
    {
        return unitOfWork.Repository<RelatedItem>().Get(c => c.Parent.Id == mainClassId && c.Date == date);
    }
}

So I've got situation when I cannot use property Items directly because of EF Core, but I should use them because of DDD architecture.

And my question is: is it ok to use such a construction?


Solution

  • From what it seems from your question, the MainClass is an Aggregate root and RelatedItem is a nested entity. This design decision should be based on the business rules/invariants that must be protected. When an Aggregate needs to mutate, it must be fully loaded from the repository, that is, the Aggregate root and all its nested entities and value object must be in memory before it execute the mutating command, no matter how big it is.

    Also, it is not a good practice to inject infrastructure services into Aggregates (or in nested entities). If you need to do this, then you must think again on your architecture.

    So, from what I wrote you can see that the problem manifest itself only when you try to mutate the Aggregate. If you only need to read it or find it, you could create some dedicated services that find the data using infrastructure components. Your MainClassService seems to be such a case, where you need only to read/find some RelatedItem entities.

    In order to be clear that the purpose is only reading, the MainClassService needs to return a readonly representation of the RelatedItem entities.

    So, you just madee some first steps towards CQRS, where the models are split into two: READ model and WRITE model.