Search code examples
c#eventsdevforce

DevForce doesn't always fire Queued Events if they are queued during a call to FireQueuedEvents


We are running into cases where we change a property on our entity but the PropertyChanged event doesn't fire. We are doing this logic as part of a Save and so the problem seems to be the way DevForce queues events like this during a Save.

Looking at the code for LoadingBlock.Dispose(), I see this:

public void Dispose()
{
    this._entityManager.FireQueuedEvents();
    this._entityManager.IsLoadingEntity = this._wasLoadingEntity;
}

There is a race condition there that you fire the queued events before changing the IsLoadingEntity property. That means that any new events that get generated during the FireQueuedEvents will get queued (since IsLoadingEntity will still be true) but events that get queued then will never get fired since we already fired the queued events (that we knew about). It seems like DevForce should reset the IsLoadingEntity flag before firing the events. I think that would solve our problems.

Here is some code that might help explain our case. I'll use a Merge call instead of SaveChanges since it's easier to use in a unit test:

//Create the main Entity Manager and a test entity
var em = new EntityManager();
var entity = new MyEntity {SID = 123};
em.AttachEntity(entity);

//Create a second copy of the entity and another Entity Manager - this is just so 
//  we can trigger a merge and see the bad behavior
var copy = new MyEntity { SID = 123, MergeCount = 20 };
var em2 = new EntityManager();
em2.AttachEntity(copy);

//This code is a bit contrived but it's similar to what we are doing in our actual app
em.EntityChanged += (sender, args) =>
{
    //If it is a MyEntity that changed and it was from a Merge, increment the MergeCount property
    var e = args.Entity as MyEntity;
    if (e != null && args.Action == EntityAction.ChangeCurrentAndOriginal)
    {
        e.MergeCount++;
    }
};

//Set up a PropertyChanged event handler to see what properties got changed (according to INotifyPropertyChanged)
var propertiesChanged = new List<string>();
entity.PropertyChanged += (sender, args) => { propertiesChanged.Add(args.PropertyName); };

//Merge the copy entity
em2.CacheStateManager.GetCacheState().Merge(em, RestoreStrategy.Normal);

//At this point, the MergeCount property will be 21 - as expected
Assert.AreEqual(21, entity.MergeCount);

//We should have seen a PropertyChanged event for MergeCount since we changed the property (it was 20 and we set it to 21)
Assert.IsTrue(propertiesChanged.Contains("MergeCount"));

//In the debugger, if we look at em._queuedEvents, we'll see some items in there.  One of the items is the PropertyChanged event
//  for MergeCount.  It 'fired' but was queued...and it will be queued forever because the LoadingBlock is long gone.

I've found that I can just do another Merge from an empty Entity Manager and that will cause the previously queued events to fire. That is an okay workaround in one case that we ran into this. But I fear there might be other places where we are running into this issue and that workaround won't work for us.


Solution

  • You're right that the IsLoadingEntity flag should be cleared at the beginning of the Dispose logic, and we'll open a bug report for this.

    If you're able to use the EntityChanging event instead of EntityChanged that also might be a workaround to try. The changing event isn't queued, so execution of the handler causes the PropertyChanged event to be processed before the LoadingBlock is disposed.