Base solution for your next web application
Open Closed

Using EventBus to fire Entity events #535


User avatar
0
byteplatz created

Hello Halil,

Im trying to understand the eventbus architecture and I have couple questions:

  1. What is the difference between EntityUpdatedEventData/EntityChangedEventData events ?

  2. Does the *ed events (Created, Updated, Changed, Deleted) are triggered after UoW has been commited ? If so, Are there any hooks I can get to do some work (transactionally) before this happen ? My scenario is :

Im using NServiceBus as a BackEnd Message System. I would like the existing EventBus (from Abp) infrastructure from the WebSide to minimize the code I need to write. The problem Is, when an Entity is created for example, I need to run a Bus.Send(onesimplecommand) -- from NServiceBus. This code must be present in the same transaction used to SAVE the entity into the database, since Im using transactional MSMQ queues. This helps me in terms of if I cant write to database, Bus.Send does not happen and if Bus.Send throws an exception, the data is not written to database. (they must be in the same transaction).

Cany ou shed some light on how I can solve this ?

Bruno


13 Answer(s)
  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    1. EntityChanged is triggered for delete/insert/update.

    2. all events are triggered in the transaction. So, if you throw exception inside event handler, transaction is rolledback, as you want.

    Have a nice day.

  • User Avatar
    0
    byteplatz created

    Thank you...

    Just wondering if there is anything special to register these pre defined events

    Should I only create a handler like this :

    public class AllEventsHandler : IEventHandler<EntityCreatedEventData<MyEntity>>,
     IEventHandler<EntityUpdatedEventData<MyEntity>>,
    IEventHandler<EntityDeletedEventData<MyEntity>>
    

    I did that and the handlers are not being invoked.

    Even tried placing this code inside web or application Project (not from my separated custom module)

    Regarding transaction I did couple tests but that was not the behavior I found at that time...I was using Bus.Send which was throwing exception but the DB were not rolling back.

    I found this code on AbpSource code:

    private void TriggerEntityChangeEvent(Type genericEventType, object entity)
            {
                var entityType = entity.GetType();
                var eventType = genericEventType.MakeGenericType(entityType);
    
                if (_unitOfWorkManager == null || _unitOfWorkManager.Current == null)
                {
                    EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, new[] { entity }));
                    return;
                }
    
                _unitOfWorkManager.Current.Completed += (sender, args) => EventBus.Trigger(eventType, (IEventData)Activator.CreateInstance(eventType, new[] { entity }));
            }
    

    Doesnt that mean that the trigger occurs after UoW has completed ?

    Bruno

  • User Avatar
    0
    hikalkan created
    Support Team

    AllEventsHandler can not work because you missed to register your class to DI. You can implement ITransientDependency simply.

    It's triggered before complete. See triggering code for EntityChanged for example: <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/master/src/Abp.EntityFramework/EntityFramework/AbpDbContext.cs#L171">https://github.com/aspnetboilerplate/as ... xt.cs#L171</a>

  • User Avatar
    0
    byteplatz created

    Thank you ! Another issue solved :) My bad on not paying attention to it...

    One more thing: I have injected a ILogger (Castle.Core.Logging) inside AllEventsHandler and Im getting it null all the time. Im using property injection since this class must have a parameterless constructor.

    Any suggestions ?

    Bruno

  • User Avatar
    0
    byteplatz created

    Never mind on this...

    I was getting null when I register the handler manually...

    Usint ITransiendDependency Works fine...

    Should It be null when manual registering (EventBus.Default.Register) ?

    Bruno

  • User Avatar
    0
    hikalkan created
    Support Team

    EventBus.Default.Register should also work. It's tested (<a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/master/src/TestBase/Tests/Abp.TestBase.SampleApplication.Tests/People/PersonRepository_Tests_For_EntityChangeEvents.cs#L24">https://github.com/aspnetboilerplate/as ... nts.cs#L24</a>). But if you believe that it does not work, please create an issue on github (<a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues">https://github.com/aspnetboilerplate/as ... ate/issues</a>). please include where did you register it.

    thanks.

  • User Avatar
    0
    byteplatz created

    Thanks

    I'm now investigating why event bus is triggering the event twice, one for each dB context.

  • User Avatar
    0
    hikalkan created
    Support Team

    Is it triggering the same event? As you can see the code triggers the event, it should not be. Please check your application. And if you are sure from that, please open an issue here with detailed description: <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues">https://github.com/aspnetboilerplate/as ... ate/issues</a> Thanks.

  • User Avatar
    0
    byteplatz created

    Hello

    I'm not triggering the event manually...I'm using EntityCreatedEventData...

    Breakpoint at Handle event is called twice...I will entity is the same...I will check every field to see if audit related fields are set in both calls

    Bruno

  • User Avatar
    0
    byteplatz created

    Both triggers are exactly the same...

    Cant find why the event is being triggered twice...

    The event handler is located in a separated module.

    I can see that the event is being triggered one for each dbcontext (MainApp and CustomModule)

    Can you help me on that ?

    Bruno

  • User Avatar
    0
    byteplatz created

    Debugging Abp, I can see that this is the flow:

    • Creating a new entity:

    • Breakpoint hit in EntityChangedEventHelper.TriggerEntityChangeEvent

    • Breakpoint hit in HandleEvent(EntityCreatedEventData<MyEntity> eventData)

    • Breakpoint hit in HandleEvent(EntityCreatedEventData<MyEntity> eventData) -- second time

    • Breakpoint hit in EntityChangedEventHelper.TriggerEntityChangeEvent

    By looking at the code I found this:

    public void Trigger(Type eventType, object eventSource, IEventData eventData)
            {
                //TODO: This method can be optimized by adding all possibilities to a dictionary.
    
                eventData.EventSource = eventSource;
    
                foreach (var factoryToTrigger in GetHandlerFactories(eventType))
                {
                    var eventHandler = factoryToTrigger.GetHandler();
                    if (eventHandler == null)
                    {
                        throw new Exception("Registered event handler for event type " + eventType.Name + " does not implement IEventHandler<" + eventType.Name + "> interface!");
                    }
    
                    var handlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
    
                    try
                    {
                        handlerType
                            .GetMethod("HandleEvent", BindingFlags.Public | BindingFlags.Instance, null, new[] { eventType }, null)
                            .Invoke(eventHandler, new object[] { eventData });
                    }
                    finally
                    {
                        factoryToTrigger.ReleaseHandler(eventHandler);
                    }
                }
    

    This code get executed when creating entity (perfectly sense), but the foreach get called twice for my handler (maybe registered twice) :

    handlerType
                            .GetMethod("HandleEvent", BindingFlags.Public | BindingFlags.Instance, null, new[] { eventType }, null)
                            .Invoke(eventHandler, new object[] { eventData });
    

    This is my Handler code (Im listening for the Changed eventdata as well and I thinkg this might be the problem with the generic event dispatcher):

    class MyEntityEventDataHandler : ITransientDependency,
            IEventHandler<EntityCreatedEventData<MyEntity>>,
            IEventHandler<EntityUpdatedEventData<MyEntity>>,
            IEventHandler<EntityChangedEventData<MyEntity>>,
            IEventHandler<EntityDeletedEventData<MyEntity>>
        {
            public ILogger Logger { get; set; }
    
            public void HandleEvent(EntityDeletedEventData<MyEntity> eventData)
            {
                Logger.Debug("EntityDeleted");
            }
    
            public void HandleEvent(EntityChangedEventData<MyEntity> eventData)
            {
                Logger.Debug("EntityChanged");
            }
    
            public void HandleEvent(EntityUpdatedEventData<MyEntity> eventData)
            {
                Logger.Debug("EntityUpdated");
            }
    
            public void HandleEvent(EntityCreatedEventData<MyEntity> eventData)
            {
                Logger.Debug("EntityCreated");
            }
        }
    

    I will remove the entityChanged and try again

    Bruno

  • User Avatar
    0
    byteplatz created

    By removing the EntityChangedEventData handler it Works fine.

    My understanding for this behavior (listening both parente/child event when inheriting) this will be the flow:

    The lower level event handler get called, in this case it should be EntityCreatedEventHandler....then, because of the inheritance, EntityChangedEventHandler...

    But the EntityCreatedEventHandler is called twice...

    Thoughts Halil ?

    Bruno

  • User Avatar
    0
    byteplatz created

    I have opened an issue on GitHub regarding transaction and UoW on EventBus when using 02 dbcontexts (from separated module)

    [https://github.com/aspnetzero/aspnet-zero/issues/85])

    I need this in a certain level of priority. If you could take a look I would greatly appreciate.

    Bruno