Print Page | Close Window

Problem with event handlers on cloned entities

Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=3990
Printed Date: 12-May-2026 at 9:33pm


Topic: Problem with event handlers on cloned entities
Posted By: stephenmcd1
Subject: Problem with event handlers on cloned entities
Date Posted: 13-Feb-2013 at 9:48am
We've been playing around with different ways to quickly move entities from one Entity Manager to another (see http://www.ideablade.com/forum/forum_posts.asp?TID=3978&title=performance-issues-with-importentities - this thread).  We've tried using the Clone method but run into problems where event handlers that subscribed to the original entity get raised when changes are made to the newly cloned entity.  It seems to be an issue with the way the EntityAspect is cloned - it starts with a MemberwiseClone which will end up giving the new aspect the same delegate used in the original aspect.

Here is some test code that might explain my problem more clearly:
        public void CloneTests()
        {
            var firstEntity = new MyEntity();

            ((INotifyDataErrorInfo)firstEntity).ErrorsChanged += (_, __) => Assert.Fail("ErrorsChanged fired on first entity");
            firstEntity.PropertyChanged += (_, __) => Assert.Fail("PropertyChanged fired on first entity");

            //Make a copy of the original entity
            var secondEntity = (MyEntity)((ICloneable)firstEntity).Clone();

            //Either force property change or change the validation errors on this *second* entity.  Both these lines will fail because
            //  they will end up executing the code that subscribed to these events on the first entity!
            secondEntity.EntityAspect.ForcePropertyChanged(new PropertyChangedEventArgs("something"));
            secondEntity.EntityAspect.ValidationErrors.Add(new VerifierResult(false));
        }

I'm hoping this isn't just an unsupported scenario.  Obviously, if I wait until after the clone to attach the event handlers, everything works as expected.  But in our code, it's not always easy to do that.  And I haven't found an easy way to work around this in our code (and the reflection restrictions in Silverlight do not help!).



Replies:
Posted By: stephenmcd1
Date Posted: 13-Feb-2013 at 11:19am
After doing some very ugly hacking in our code to try to work around the issue above, I've run into another problem with the Clone logic.  And I don't think there will be any way I can work around this one.  Luckily, it seems even more clearly a bug and probably even easier to fix than the issue above.

The problem happens when you clone an entity that already has validation errors.  In that case, the Entity Aspect cloning logic will create a new collection to hold the errors - which is correct.  But it passes the old entity aspect to the collection so things get a bit messed up.  The relevant code is in EntityAspect.CloneCore() and looks like this:
    if (this._validationErrors != null)
    {
        aspect._validationErrors = new VerifierErrorsCollection(this, this._validationErrors);
    }

Note that is is giving 'aspect' (the new aspect) a new VerifierErrorsCollection.  But it's passing 'this' (the source aspect) to the collection!  So the new aspect is holding onto a collection and the collection thinks it is associated with the old aspect!  It seems that this same kind of bug exists in other places in that method (the OriginalValuesMap and BackupValuesMap fields jump out).

Here is some test code to help explain things:
        public void CloneTests2()
        {
            //Make an entity with validation errors
            var firstEntity = new MyEntity();
            firstEntity.EntityAspect.ValidationErrors.Add(new VerifierResult(false));

            //Make a copy of the original entity
            var secondEntity = (MyEntity)((ICloneable)firstEntity).Clone();

            //The validations errors will correctly get copied to the cloned entity.
            Assert.AreEqual(1, secondEntity.EntityAspect.ValidationErrors.Count);//Pass

            //But the VerifierErrorsCollection on the cloned entity isn't quite setup right.  For example...

            //See if ErrorsChanged fires on the second entity
            var errorChangeFiredOnSecondEntity = false;
            ((INotifyDataErrorInfo)secondEntity).ErrorsChanged += (_, __) => errorChangeFiredOnSecondEntity = true;
            
            //Clear out the errors on the second entity to see what happens
            secondEntity.EntityAspect.ValidationErrors.Clear();

            //The error was correctly removed
            Assert.AreEqual(0, secondEntity.EntityAspect.ValidationErrors.Count);//Pass

            //However, this line will fail because ErrorsChanged was not raised on the second entity.  In fact, it was raised on the 
            //  first entity - even though the errors on the first entity weren't touched.
            Assert.IsTrue(errorChangeFiredOnSecondEntity);//Fail

            //Further support for the VerifierErrorsCollection being messed up....let's check out the _entityAspect field on the collection

            //On the first entity, the aspect is correct
            Assert.AreSame(firstEntity.EntityAspect, GetAspect(firstEntity.EntityAspect.ValidationErrors));//Pass

            //However, these two lines will fail.  We'd expect the aspect of the second collection to be the same as the second 
            //  entity's aspect...but it's not!  And in fact, the aspect is still pointing to the first entity
            Assert.AreSame(secondEntity.EntityAspect, GetAspect(secondEntity.EntityAspect.ValidationErrors));//Fail
            Assert.AreNotSame(firstEntity.EntityAspect, GetAspect(secondEntity.EntityAspect.ValidationErrors));//Fail
        }

        private EntityAspect GetAspect(EntityAspect.VerifierErrorsCollection collection)
        {
            //Ugly code to get at the private field
            return (EntityAspect)typeof(EntityAspect.VerifierErrorsCollection)
                                     .GetField("_entityAspect", BindingFlags.Instance | BindingFlags.NonPublic)
                                     .GetValue(collection);
        }


Posted By: sbelini
Date Posted: 15-Feb-2013 at 2:26pm
Hi stephenmcd1,

We are investigating these problems and will keep you informed.

sbelini.


Posted By: sbelini
Date Posted: 18-Feb-2013 at 1:48pm
Hi stephenmcd1,

I wanted to let you know that we have fixed these issues.
They will be available in the next release of DevForce 2010.

sbelini


Posted By: stephenmcd1
Date Posted: 20-Feb-2013 at 8:42am
That is great news.  Thanks for the update.


Posted By: stephenmcd1
Date Posted: 25-Feb-2013 at 3:21pm
Any estimate on when the next release will be available?  We are looking to upgrade soon.


Posted By: sbelini
Date Posted: 25-Feb-2013 at 4:49pm
We're planning a new release mid-March.


Posted By: stephenmcd1
Date Posted: 15-Mar-2013 at 4:09pm
Any update on when this release will be available.  We are eagerly awaiting the release and I'd say it is now technically mid-March :-).

Thanks,
-Stephen


Posted By: kimj
Date Posted: 15-Mar-2013 at 4:51pm
We plan to release it next Tuesday, 3/19.


Posted By: stephenmcd1
Date Posted: 15-Mar-2013 at 5:18pm
Great, thanks!



Print Page | Close Window