diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs new file mode 100644 index 0000000000..20556f16ff --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.DynamicProxy; +using Volo.Abp.EventBus; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Local; +using Volo.Abp.Uow; + +namespace Volo.Abp.Domain.Entities.Events; + +/// +/// Used to trigger entity change events. +/// +public class EntityChangeEventHelper : IEntityChangeEventHelper, ITransientDependency +{ + private const string UnitOfWorkEventRecordEntityPropName = "_Abp_Entity"; + + public ILogger Logger { get; set; } + public ILocalEventBus LocalEventBus { get; set; } + public IDistributedEventBus DistributedEventBus { get; set; } + + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IEntityToEtoMapper EntityToEtoMapper { get; } + protected AbpDistributedEntityEventOptions DistributedEntityEventOptions { get; } + + public EntityChangeEventHelper( + IUnitOfWorkManager unitOfWorkManager, + IEntityToEtoMapper entityToEtoMapper, + IOptions distributedEntityEventOptions) + { + UnitOfWorkManager = unitOfWorkManager; + EntityToEtoMapper = entityToEtoMapper; + DistributedEntityEventOptions = distributedEntityEventOptions.Value; + + LocalEventBus = NullLocalEventBus.Instance; + DistributedEventBus = NullDistributedEventBus.Instance; + Logger = NullLogger.Instance; + } + + public virtual void PublishEntityCreatedEvent(object entity) + { + TriggerEventWithEntity( + LocalEventBus, + typeof(EntityCreatedEventData<>), + entity, + entity + ); + + if (ShouldPublishDistributedEventForEntity(entity)) + { + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityCreatedEto<>), + eto, + entity + ); + } + } + } + + private bool ShouldPublishDistributedEventForEntity(object entity) + { + return DistributedEntityEventOptions + .AutoEventSelectors + .IsMatch( + ProxyHelper + .UnProxy(entity) + .GetType() + ); + } + + public virtual void PublishEntityUpdatedEvent(object entity) + { + TriggerEventWithEntity( + LocalEventBus, + typeof(EntityUpdatedEventData<>), + entity, + entity + ); + + if (ShouldPublishDistributedEventForEntity(entity)) + { + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityUpdatedEto<>), + eto, + entity + ); + } + } + } + + public virtual void PublishEntityDeletedEvent(object entity) + { + TriggerEventWithEntity( + LocalEventBus, + typeof(EntityDeletedEventData<>), + entity, + entity + ); + + if (ShouldPublishDistributedEventForEntity(entity)) + { + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityDeletedEto<>), + eto, + entity + ); + } + } + } + + protected virtual void TriggerEventWithEntity( + IEventBus eventPublisher, + Type genericEventType, + object entityOrEto, + object originalEntity) + { + var entityType = ProxyHelper.UnProxy(entityOrEto).GetType(); + var eventType = genericEventType.MakeGenericType(entityType); + var eventData = Activator.CreateInstance(eventType, entityOrEto); + var currentUow = UnitOfWorkManager.Current; + + if (currentUow == null) + { + Logger.LogWarning("UnitOfWorkManager.Current is null! Can not publish the event."); + return; + } + + var eventRecord = new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext()) + { + Properties = + { + { UnitOfWorkEventRecordEntityPropName, originalEntity }, + } + }; + + /* We are trying to eliminate same events for the same entity. + * In this way, for example, we don't trigger update event for an entity multiple times + * even if it is updated multiple times in the current UOW. + */ + + if (eventPublisher == DistributedEventBus) + { + currentUow.AddOrReplaceDistributedEvent( + eventRecord, + otherRecord => IsSameEntityEventRecord(eventRecord, otherRecord) + ); + } + else + { + currentUow.AddOrReplaceLocalEvent( + eventRecord, + otherRecord => IsSameEntityEventRecord(eventRecord, otherRecord) + ); + } + } + + public bool IsSameEntityEventRecord(UnitOfWorkEventRecord record1, UnitOfWorkEventRecord record2) + { + if (record1.EventType != record2.EventType) + { + return false; + } + + var record1OriginalEntity = record1.Properties.GetOrDefault(UnitOfWorkEventRecordEntityPropName) as IEntity; + var record2OriginalEntity = record2.Properties.GetOrDefault(UnitOfWorkEventRecordEntityPropName) as IEntity; + + if (record1OriginalEntity == null || record2OriginalEntity == null) + { + return false; + } + + return EntityHelper.EntityEquals(record1OriginalEntity, record2OriginalEntity); + } +}