httpflow icon indicating copy to clipboard operation
httpflow copied to clipboard

Please support domain event publishing of 'EntityBase<TId>'

Open SystematicChaos012 opened this issue 2 years ago • 3 comments

A new entity abstract class 'EntityBase<TId>' has been added in the latest version, but in the SaveChangesAsync method under DbContext, the entity of 'EntityBase' cannot be obtained through ChangeTracker.

Because 'EntityBase<TId>' does not inherit from 'EntityBase'

I originally planned to get the entities that need to publish domain events through their common inheritance, that is, 'HasDomainEventsBase', but I failed when I tried to ClearDomainEvents because it has an internal access level.

So I hope to add support for 'EntityBase<TId>' in the future

SystematicChaos012 avatar Nov 28 '23 19:11 SystematicChaos012

Yeah, this behavior should be supported. I thought I'd verified it was working. Can you provide some sample code that isn't working?

ardalis avatar Nov 28 '23 19:11 ardalis

Yeah, this behavior should be supported. I thought I'd verified it was working. Can you provide some sample code that isn't working?

@ardalis Yes,

In Core

public class Category : EntityBase<uint>, IAggregateRoot
{
    public uint ParentId { get; private set; }

    public string Name { get; private set; } = default!;

    protected Category() { }

    public Category(uint parentId, string name)
    {
        ParentId = parentId;
        Name = Guard.Against.NullOrWhiteSpace(name, nameof(name));

        RegisterDomainEvent(new CategoryCreatedEvent(parentId, name));
    }
}

In UseCases

public record CreateCategoryCommand(uint ParentId, string Name) : ICommand<Result<uint>>;

public class CreateCategoryHandler(IRepository<Category> repository) : ICommandHandler<CreateCategoryCommand, Result<uint>>
{
    public async Task<Result<uint>> Handle(CreateCategoryCommand request, CancellationToken cancellationToken)
    {
        var result = await repository.AddAsync(new Category(request.ParentId, request.Name), cancellationToken);
        return result.Id;
    }
}

In Infrastructure

public class AppDbContext(DbContextOptions<AppDbContext> options, IDomainEventDispatcher? dispatcher) : DbContext(options)
{
  public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
  {
    if (dispatcher == null) return await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

    // bug here
    // ChangeTracker.Entries<EntityBase>() cannot return instances of 'EntityBase<TId>'
    // because there is no inheritance relationship between EntityBase<TId> and EntityBase
    var entitiesWithEvents = ChangeTracker.Entries<EntityBase>()
        .Select(e => e.Entity)
        .Where(e => e.DomainEvents.Any())
        .ToArray();

    // Task DispatchAndClearEvents(IEnumerable<EntityBase> entitiesWithEvents);
    // DispatchAndClearEvents method only accepts IEnumerable<EntityBase>
    await dispatcher.DispatchAndClearEvents(entitiesWithEvents);

    return await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);;
  }

  public override int SaveChanges()
  {
    return SaveChangesAsync().GetAwaiter().GetResult();
  }
}

Maybe make DispatchAndClearEvents accept IEnumerable<HasDomainEventsBase>

SystematicChaos012 avatar Nov 28 '23 21:11 SystematicChaos012

@ardalis just wanted to check in if you're planning to knock this off. This one is bugging me as well. Thanks...

rameshdabhi avatar Jan 23 '24 12:01 rameshdabhi