Search code examples
.netentity-framework-coredomain-driven-design

DDD EF Core Is it possible to load entity with sub-entities without the navigation property for the collection?


I'm building a DDD application with CQRS. In my aggregate I have a Budget aggregate root and BudgetedTransaction entity. Budget keeps track of the transactions, the list of BudgetedTransactions is the property of the Budget. In the database that's a 1-to-many relationship between the Budget and BudgetedTransaction.

My Budget class:

public class Budget : Entity, IAggregateRoot
{
    private readonly List<BudgetedTransaction> _budgetedTransactions;

    private Budget() { } //ef core

    public Budget(List<BudgetedTransaction> budgetedTransactions)
    {
        _budgetedTransactions = budgetedTransactions;
    }

    public void AddBudgetedTransaction(BudgetedTransaction transaction)
    {
        _budgetedTransactions.Add(transaction);
    }
}

In my command handler I'm using EF core to retrieve the Budget, along with the transactions using Include method in the repo

public async Task<BudgetedTransactionDto> Handle(AddBudgetedTransactionCommand command, CancellationToken cancellationToken)
{
    var budget = await _budgetRepository.GetBudgetById(command.AddBudgetedTransactionRequest.BudgetId); 
            
    var transaction = new BudgetedTransaction(
    _guidService.NewGuid,
    command.AddBudgetedTransactionRequest.BudgetId,
    command.AddBudgetedTransactionRequest.BudgetingCategoryId,
    command.AddBudgetedTransactionRequest.TransactionType,
    command.AddBudgetedTransactionRequest.TransactionOccurrences,
    command.AddBudgetedTransactionRequest.StartDate,
    command.AddBudgetedTransactionRequest.Amount,
    _dateTimeService.UtcNow);

    budget.AddBudgetedTransaction(transaction);

    await _budgetRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

    budget.AddDomainEvent(new BudgetedTransactionAdded(transaction));

    return await _transactionsRepositoryRead.GetBudgetedTransactionById(transaction.Id);
}

BudgetRepository:

internal class BudgetRepository : IBudgetRepository
{
    private readonly BudgetingDbContext _dbContext;

    public BudgetRepository(BudgetingDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IUnitOfWork UnitOfWork => _dbContext;

    public async Task<Budget> GetBudgetById(Guid budgetId)
    {
        return await _dbContext.Budgets
            .Where("BudgetedTransactions")
            .Include(b => b.)
            .SingleAsync();
    }
}

And finally my entity configuration. For the migration purposes I managed to avoid adding the navigation property to Budget.

public class BudgetedTransactionEntityTypeConfiguration : IEntityTypeConfiguration<BudgetedTransaction>
{
    public void Configure(EntityTypeBuilder<BudgetedTransaction> builder)
    {
        builder
            .ToTable("BudgetedTransaction")
            .HasKey(t => t.Id);

        builder
            .Property<Guid>("Id")
            .IsRequired();

        builder.HasOne<Budget>()
            .WithMany();

        builder
            .Property<Guid>("BudgetId")
            .HasColumnType("uniqueidentifier")
            .IsRequired();
   }
}

However I'm getting the exception when retrieving the budget from DB: System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Query.InvalidIncludePathError': Unable to find navigation 'BudgetedTransactions' specified in string based include path 'BudgetedTransactions'. This exception can be suppressed or logged by passing event ID 'CoreEventId.InvalidIncludePathError' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.

I understand the problem, I'm providing the name of non-existing navigation property. Is it then otherwise possible to load the Budget with Transactions and not create the navigation property?


Solution

  • If you want use navigational properties as include, must add it as public

    public Budget {
        ....
        private readonly List<BudgetedTransaction> _budgetedTransactions;
        public IEnumerable<BudgetedTransaction> BudgetedTransactions => _budgetedTransactions?.ToList()
        ....
    }