OnSetRelatedEntity timing

Posts   
 
    
blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 12-Mar-2008 06:25:38   

We have an area in our application that uses the IsDirty flags on the entities in a graph to decide whether the root entity should be saved. This has worked quite well up to this point.

We just introduced a 1:1 relation in one of these root entities. The problem is that when the related entity is set, there is no PropertyChanged message fired. This means that when we set entity.RelatedEntity = new RelatedEntity(); there's nothing to tell our code that the entity has changed and we should look at the graph again.

Searching the forums yielded this:

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=8719

Did you ever implement the PropertyChanged event for this case? Our generated code doesn't seem to send these events (ORM Support Classes is version 2.5.7.827 and we're using version 2.5 final).

We tried extending CommonEntityBase to hook into OnRelatedEntityUnset and OnRelatedEntitySet, like so:

        protected override void OnRelatedEntityUnset(IEntity2 relatedEntity, string fieldName)
        {
            base.OnRelatedEntityUnset(relatedEntity, fieldName);

            OnRelatedEntityChanged(relatedEntity);
        }

        // The same for OnRelatedEntitySet
    
        protected void OnRelatedEntityChanged(IEntity2 relatedEntity)
        {
            CommonEntityBase relatedEntityCommonEntityBase = relatedEntity as CommonEntityBase;

            if (relatedEntityCommonEntityBase.RelatedEntityChanged != null)
            {
                relatedEntityCommonEntityBase.RelatedEntityChanged(this, new RelatedEntityChangedEventArgs());
            }
        }

However, it seems these are called before the property is actually set on the entity. Is there any good way to fire our RelatedEntityChanged event that then allows our bindings to go back and look at the new value? Is there a version we can upgrade to that sends a PropertyChanged message when you set entity.RelatedEntity with PropertyChangedEventArgs("RelatedEntity")?

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 12-Mar-2008 09:29:15   

ORM Support Classes is version 2.5.7.827 and we're using version 2.5 final

That's a rather old version would you please try the latest.

As far as I can see, when setting a related entity the following happens:

1- Synchronization is done (mapped field is set, then PK-FK synchronization is done) 2- Then OnRelatedEntitySet / unset is called

I think you may use the OnRelatedEntitySet/unset to call OnPropertyChange.

blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 18-Mar-2008 01:51:42   

Hi Walaa,

I have the following unit test:

        public void Go()
        {
            UserEntity user = new UserEntity();

            int eventCalledCount = 0;
            UserCertificateEntity eventCalledCertificate = null;

            user.RelatedEntityChanged += new RelatedEntityChangedEventHandler(
                delegate(object sender, RelatedEntityChangedEventArgs e)
                {
                    eventCalledCount++;

                    // Save the certificate to ensure that at the time of the event the
                    // property has been updated:
                    eventCalledCertificate = user.Certificate;
                });

            // Try setting the certificate:
            user.Certificate = new UserCertificateEntity();

            Assert.AreEqual(1, eventCalledCount);
            Assert.IsTrue(object.ReferenceEquals(user.Certificate, eventCalledCertificate));

            // Try setting a different certificate:
            user.Certificate = new UserCertificateEntity();

            Assert.AreEqual(2, eventCalledCount);
            Assert.IsTrue(object.ReferenceEquals(user.Certificate, eventCalledCertificate));

            // Try clearing the certificate:
            user.Certificate = null;

            Assert.AreEqual(3, eventCalledCount);
            Assert.IsNull(eventCalledCertificate);
        }

User and UserCertificate are 1:1. The unit test fails on this line:

Assert.IsTrue(object.ReferenceEquals(user.Certificate, eventCalledCertificate));

Here's the code I put in CommonEntityBase:

    public partial class CommonEntityBase : INotifyRelatedEntityChanged
    {
        #region INotifyRelatedEntityChanged Members

        public event RelatedEntityChangedEventHandler RelatedEntityChanged;

        protected override void OnRelatedEntitySet(IEntity2 relatedEntity, string fieldName)
        {
            base.OnRelatedEntitySet(relatedEntity, fieldName);

            OnRelatedEntityChanged(relatedEntity);
        }

        protected override void OnRelatedEntityUnset(IEntity2 relatedEntity, string fieldName)
        {
            base.OnRelatedEntityUnset(relatedEntity, fieldName);

            OnRelatedEntityChanged(relatedEntity);
        }
    
        protected void OnRelatedEntityChanged(IEntity2 relatedEntity)
        {
            CommonEntityBase relatedEntityCommonEntityBase = relatedEntity as CommonEntityBase;

            if (relatedEntityCommonEntityBase.RelatedEntityChanged != null)
            {
                relatedEntityCommonEntityBase.RelatedEntityChanged(this, new RelatedEntityChangedEventArgs());
            }
        }

        #endregion
    }

I must be missing something. It looks like OnRelatedEntitySet and Unset are called before the actual related entity has changed. I need the user to tell me that its certificate has changed.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 18-Mar-2008 10:28:06   

Walaa wrote:

1- Synchronization is done (mapped field is set, then PK-FK synchronization is done) 2- Then OnRelatedEntitySet / unset is called

I was speaking about the PK-FK synchronization. Not the object property.

user.CertificateId = Certificate.Id

That's done before the OnRelatedEntitySet call.

blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 19-Mar-2008 00:17:11   

Walaa wrote:

I was speaking about the PK-FK synchronization. Not the object property.

user.CertificateId = Certificate.Id

That's done before the OnRelatedEntitySet call.

That makes sense. Is there a good way for us to implement this functionality?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 19-Mar-2008 09:49:54   

Walaa wrote:

As far as I can see, when setting a related entity the following happens:

1- Synchronization is done (mapped field is set, then PK-FK synchronization is done) 2- Then OnRelatedEntitySet / unset is called

I think you may use the OnRelatedEntitySet/unset to call OnPropertyChange.

blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 19-Mar-2008 23:19:41   

Ok, this is what I have in CommonEntityBase:

        protected override void OnRelatedEntitySet(IEntity2 relatedEntity, string fieldName)
        {
            OnPropertyChanged(fieldName);
        }

        protected override void OnRelatedEntityUnset(IEntity2 relatedEntity, string fieldName)
        {
            OnPropertyChanged(fieldName);
        }

And this is my unit test:

        [Test]
        public void Go()
        {
            UserEntity user = new UserEntity();

            int eventCalledCount = 0;
            UserCertificateEntity eventCalledCertificate = null;

            user.PropertyChanged += new PropertyChangedEventHandler(
                delegate(object sender, PropertyChangedEventArgs e)
                {
                    if (e.PropertyName == "Certificate")
                    {
                        eventCalledCount++;

                        // Save the certificate to ensure that at the time of the event the
                        // property has been updated:
                        eventCalledCertificate = user.Certificate;
                    }
                });

            // Try setting the certificate:
            user.Certificate = new UserCertificateEntity();

            Assert.AreEqual(1, eventCalledCount);
            Assert.IsTrue(object.ReferenceEquals(user.Certificate, eventCalledCertificate));

            // Try setting a different certificate:
            user.Certificate = new UserCertificateEntity();

            Assert.AreEqual(2, eventCalledCount);
            Assert.IsTrue(object.ReferenceEquals(user.Certificate, eventCalledCertificate));

            // Try clearing the certificate:
            user.Certificate = null;

            Assert.AreEqual(3, eventCalledCount);
            Assert.IsNull(eventCalledCertificate);
        }

This test fails on the first Assert.AreEqual line, because OnRelatedEntitySet is called in the user certificate entity, not the user.

So, could you please give me an example of how to implement this functionality? I can't call the user's OnPropertyChanged method from UserCertificate code.

Even if I could, what field name would I use?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39903
Joined: 17-Aug-2003
# Posted on: 20-Mar-2008 11:12:50   

FIrst question: why are you using logic to determine if the parent has to be saved if that's already build in? simple_smile

Anyway, it's a bug in the template: in 1:1 relations, the side which property is set isn't calling the OnRelatedEntitySet/Unset method. For all other relation types, both sides call the right methods.

So in your code, if you do: new UserCertificateEntity().User = user;

it will work. Not ideal, but it's a workaround till the fix is done.

(edit). The fix is in the next build. The bug will be present in v2.6's beta for a while, as the code is scheduled to get some refactoring so the call to OnRelatedEntitySet/Unset is done inside the runtimelib.

(edit) Attached you'll find the updated template. Place this template in Templates\SharedTemplates\Net2.x\C#

Frans Bouma | Lead developer LLBLGen Pro
blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 25-Mar-2008 00:21:57   

Otis wrote:

FIrst question: why are you using logic to determine if the parent has to be saved if that's already build in? simple_smile

I'm sorry. Reading my post again, I see that I didn't explain myself very well.

We watch the IsDirty flag on all the entities in a particular root entity's graph to determine if the save button on an inspector form is enabled. You can move between various entities in a list to show them in these inspector forms. We use the recursive save functionality to save the entity (and children) if necessary, but in this case our code didn't know to check the IsDirty flags for the entity and its children because there was no property changed or list changed method being fired.

I'll hook your new template in and see how we go. Thanks for your help. simple_smile

blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 25-Mar-2008 01:11:35   

Your new template fixes the first issue, but the unit test is still failing here:

// Try clearing the certificate:
user.Certificate = null;

Assert.AreEqual(3, eventCalledCount);

I stepped through the user.Certificate = null; call and it looks like the PerformDesetupSyncRelatedEntity call still isn't calling OnRelatedEntityUnset. disappointed

The first issue being fixed helps us a lot, and I do appreciate the time you're putting in to helping us through this. Thank you.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39903
Joined: 17-Aug-2003
# Posted on: 25-Mar-2008 10:47:07   

blarg wrote:

Otis wrote:

FIrst question: why are you using logic to determine if the parent has to be saved if that's already build in? simple_smile

I'm sorry. Reading my post again, I see that I didn't explain myself very well.

We watch the IsDirty flag on all the entities in a particular root entity's graph to determine if the save button on an inspector form is enabled. You can move between various entities in a list to show them in these inspector forms. We use the recursive save functionality to save the entity (and children) if necessary, but in this case our code didn't know to check the IsDirty flags for the entity and its children because there was no property changed or list changed method being fired.

I'll hook your new template in and see how we go. Thanks for your help. simple_smile

It's very easy to determine if there's a dirty entity in the graph. In the class ObjectGraphUtils, in the ORMSupportClasses, you can pass in an entity or entity collection and it will produce save queues for you. If these are empty, there's no dirty entity. If they're not empty, you have a save in progress. You can also traverse the queues for the entities to see if they match your criteria. It's a very fast routine so you won't see any slowdown in your form.

The call to the unset event handler is indeed not done, I'll correct that in the template as well.

(edit). I've attached a new version of the template. The code was there but only for 1-sided relations, which wasn't correct. I've altered it a bit so it should work in your situation now. It's done this way because it otherwise could give infinite loops.

Frans Bouma | Lead developer LLBLGen Pro
blarg
User
Posts: 27
Joined: 27-Nov-2007
# Posted on: 26-Mar-2008 00:57:03   

That new template works beautifully. Thanks!