OnPropertyChanged seems to fire differently between versions

Posts   
 
    
Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 16-Apr-2018 23:16:29   

I am currently upgrading from LLBLGen 4.2 to 5.3. I am using VB, Adapter model.

If I have an entity with a FK field and navigator (ProductType_ID and ProductType), I sometimes use the OnPropertyChanged method on the entity to raise OnPropertyChanged events back to my UI. If I set the navigator property to an entity in 4.2 and check the values of ProductType_ID and ProductType in the OnPropertyChanged method, I get what I expect. I see the new FK value and entity in those properties. In 5.3, I see that the FK value has been set (and thus caused the OnPropertyChanged), but the navigator property is still empty. It is as if the old version used to wait for the navigator to be set before calling the OnPropertyChanged method. This is problematic for me in that I may want to use information from the related entity to determine if I want to raise my event. It seems to me there is a window where these two properties are not in sync. Is this the designed behavior in the newer version, or am I missing something?

I'm hoping for the latter. Thanks.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 17-Apr-2018 08:16:09   

IMHO, that should work similar way. Please show us the code that triggers the OnPropertyChanged event handler. I also thinsk that maybe OnRelatedEntitySet is a better place to put your logic.

David Elizondo | LLBLGen Support Team
Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 17-Apr-2018 20:53:28   

I think I see the difference.

In 4.2, SetupSync* code generated in the entities looked something like this:

Private Sub SetupSyncProductSubtype(relatedEntity As IEntityCore) If Not _productSubtype Is relatedEntity Then DesetupSyncProductSubtype(True, True) _productSubtype = CType(relatedEntity, ProductSubtypeEntity) Me.PerformSetupSyncRelatedEntity( _productSubtype, AddressOf OnProductSubtypePropertyChanged, "ProductSubtype", Coda.Entities.RelationClasses.StaticPurchaseOrderItemRelations.ProductSubtypeEntityUsingProductSubType_IDStatic, True, New String() { } ) End If End Sub

In 5.3, the code was refactored to do this:

Private Sub SetupSyncProductSubtype(relatedEntity As IEntityCore) SetupSync(relatedEntity, _productSubtype, AddressOf OnProductSubtypePropertyChanged, "ProductSubtype", "", Coda.Entities.RelationClasses.StaticPurchaseOrderItemRelations.ProductSubtypeEntityUsingProductSubType_IDStatic, true, new String() { }, new Integer() { CInt(PurchaseOrderItemFieldIndex.ProductSubType_ID) }) End Sub

In the 5.3 case, _productSubtype is sent by reference. SetupSync looks like this:

protected void SetupSync<T>(IEntityCore relatedEntity, ref T member, PropertyChangedEventHandler handler, string fieldName, string fieldInRelatedEntity, IEntityRelation relation, bool connectToSaveEvent, string[] forfNames, int[] fkFieldIndexes) where T : class, IEntityCore { if(member!=relatedEntity) { DesetupSync(true, true, ref member, handler, fieldName, fieldInRelatedEntity, relation, true, fkFieldIndexes); member = (T)relatedEntity; this.PerformSetupSyncRelatedEntity( member, handler, fieldName, relation, connectToSaveEvent, forfNames ); } }

The problem here is that this.PerformSetupSyncRelatedEntity calls OnPropertyChanged, but the passed reference (member) has not been resolved back to _productSubType until the function is complete. So, if my overridden OnPropertyChanged code depends on _productSubType being set (as it was in 4.2), I'm in a bit of a bind.

I should probably use "ProductSubType" as the fieldName instead of "ProductSubType_ID" in OnPropertyChanged anyway. I realize there's not much you guys can do here, but I thought I'd let you know that there is a difference in behavior between the versions. Not really looking forward to going through all of my OnPropertyChanged methods, but it's probably cleaner in the long run.

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 18-Apr-2018 04:32:29   

Which 5.3 are you using? Please provide the full LLBLGen Pro runtime version number.

Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 18-Apr-2018 16:20:05   

5.3.4.0

Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 18-Apr-2018 20:32:25   

My solution of changing the OnPropertyChanged code to use the navigator property does not work either. Apparently, when OnPropertyChanged is called, the navigator has not been set yet (in the same way the FK is not set). Is this a design flaw? I imagine that you wouldn't want call OnPropertyChanged until after the related property had actually changed. The series of events is as follows:

  1. Properties are nothing when a new entity is created: ProductSubType = nothing ProductSubType_ID = nothing

  2. I set ProductSubType to an existing ProductSubTypeEntity (PK = "A")

  3. OnPropertyChanged is called by LLBLGen with propertyName = "ProductSubType_ID" At this point, ProductSubType = nothing and ProductSubType_ID = "A"

  4. OnPropertyChanged is called by LLBLGen with propertyName = "ProductSubType" At this point, ProductSubType = nothing and ProductSubType_ID = "A"

ProductSubType should be set to the related entity, no?

Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 18-Apr-2018 22:19:58   

So the attached code demonstrates the issue at hand.

In the attached code, the call into SetRef (which is modeled directly after the code found in SetupSync) runs differently depending on the declaration of the backing field in the Entity. The expected behavior would be that it doesn’t matter, however it appears that it does.

When you run the application with Private _new_method As String uncommented, the behavior runs as one would expect. The line of code ‘member = CType(val, T)’ immediately sets member and as a result sets _new_method to resulting string. However, when you uncomment the line with Private WithEvents _new_method As String, the behavior is not as expected. The line of code ‘member = CType(val, T)’ does not resolve until the return of SetRef in the SetProp method. At that point _new_method has been correctly set.

As you can see, this creates issues when you want to evaluate property change events, as the property has NOT be completely set until the SetRef has been resolved.

Running the sample application demonstrates this issue.

Attachments
Filename File size Added on Approval
Module1.txt 2,914 18-Apr-2018 22:20.11 Approved
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 18-Apr-2018 22:56:09   

Did you try daelmo suggestion?

I also thinsk that maybe OnRelatedEntitySet is a better place to put your logic

Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 18-Apr-2018 23:09:03   

Please read my last messages related to this issue. If I am conveying the issue properly, I think you would agree that using OnRelatedEntitySet results in the same problem (Yes, I did try it.)

My concern is related to OnPropertyChanged (or OnRelatedEntitySet...) for a navigator being called before the private field for the navigator is actually set. The sample code is an attempt to isolate the issue related to using WithEvents in private variables used to store the navigator private field.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 19-Apr-2018 10:54:56   

Conrad wrote:

I think I see the difference.

In 4.2, SetupSync* code generated in the entities looked something like this:

Private Sub SetupSyncProductSubtype(relatedEntity As IEntityCore) If Not _productSubtype Is relatedEntity Then DesetupSyncProductSubtype(True, True) _productSubtype = CType(relatedEntity, ProductSubtypeEntity) Me.PerformSetupSyncRelatedEntity( _productSubtype, AddressOf OnProductSubtypePropertyChanged, "ProductSubtype", Coda.Entities.RelationClasses.StaticPurchaseOrderItemRelations.ProductSubtypeEntityUsingProductSubType_IDStatic, True, New String() { } ) End If End Sub

In 5.3, the code was refactored to do this:

Private Sub SetupSyncProductSubtype(relatedEntity As IEntityCore) SetupSync(relatedEntity, _productSubtype, AddressOf OnProductSubtypePropertyChanged, "ProductSubtype", "", Coda.Entities.RelationClasses.StaticPurchaseOrderItemRelations.ProductSubtypeEntityUsingProductSubType_IDStatic, true, new String() { }, new Integer() { CInt(PurchaseOrderItemFieldIndex.ProductSubType_ID) }) End Sub

In the 5.3 case, _productSubtype is sent by reference. SetupSync looks like this:

protected void SetupSync<T>(IEntityCore relatedEntity, ref T member, PropertyChangedEventHandler handler, string fieldName, string fieldInRelatedEntity, IEntityRelation relation, bool connectToSaveEvent, string[] forfNames, int[] fkFieldIndexes) where T : class, IEntityCore { if(member!=relatedEntity) { DesetupSync(true, true, ref member, handler, fieldName, fieldInRelatedEntity, relation, true, fkFieldIndexes); member = (T)relatedEntity; this.PerformSetupSyncRelatedEntity( member, handler, fieldName, relation, connectToSaveEvent, forfNames ); } }

The problem here is that this.PerformSetupSyncRelatedEntity calls OnPropertyChanged, but the passed reference (member) has not been resolved back to _productSubType until the function is complete. So, if my overridden OnPropertyChanged code depends on _productSubType being set (as it was in 4.2), I'm in a bit of a bind.

No, that's not correct (In C# it isn't at least, and this is C# code). member = (T)relatedEntity; sets the member, so calling PerformSetupSyncRelatedEntity() after that would mean it is set. In the debugger I can clearly see the member is set after that line. The code blocks therefore are exactly the same.

Perhaps this is a VB thing? In C# this works fine. In the debugger in your case, you see the member is still empty/old value, including the property of the containing entity?

The FK field is set in the call to SetEntitySyncInformation() on line 1044 in EntityCore, the navigator member is then already set, the property changed event for the navigator member is then issued on line 1057.

Will try your repro now.

(edit) your repro gives:

Current (Before Property Set) val is null
Current (OnPropChange) val is: The value has been set
Current (PropChangeEvent) val is: The value has been set
Current (After Property Set) val is: The value has been set
Press any key to continue . . .

in a VB.NET (.NET 4.7.1) console project which to me is exactly what it should be? (member is set before the onproperychanged, checked in the debugger too)

What puzzles me a bit is this:

I sometimes use the OnPropertyChanged method on the entity to raise OnPropertyChanged events back to my UI.

you call that method yourself too, or do you mean: you use the PropertyChanged event to raise events back to the UI?

Frans Bouma | Lead developer LLBLGen Pro
Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 19-Apr-2018 17:04:32   

In C#, you are correct. This is not a problem.

I am generating VB code. In the generated VB entities, all of the internal navigator variables have a WithEvents declaration. This is what is causing the problem. You are correct in that C# does not have this issue as there is no WithEvents feature. This is a VB.Net problem only. Here's a related StackOverflow post describing the issue:

https://stackoverflow.com/questions/41836346/delayed-assignment-to-a-withevents-backing-field

If I remove WithEvents from the Class Member Declarations region in the VB entityAdapter.template, this problem goes away.

In the repro code I sent, please comment out the

Private _new_method As String

line and uncomment the

'Private WithEvents _new_method As String

line and you will see the behavior I'm talking about (I hope).

I think I confused you a bit in relation to your last question. I meant that I just handle the propertychanged event in the UI. Sorry for the confusion.

Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 19-Apr-2018 20:13:28   

FYI. In the interim, I am using the 4.2 VB version of entityIncludeAdapter.template in 5.3 to get around the issue. The code generated from that template works because it doesn't pass the navigator variable byref.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 19-Apr-2018 21:01:09   

Thanks for the feedback. Glad you have a workaround.

We'll look into it.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 20-Apr-2018 10:58:08   

aha! ok, this is pretty simple I think: we want the C# behavior, and the WithEvents isn't needed. I think it's a leftover from the time when we did hook up events in the generated code but that's been refactored some time ago and all events are hooked up in the base class and are never set implicitly: they're explicitly added/removed, so the syntactic sugar from WithEvents isn't needed. (and also unwanted, as it converts the field into a property)

We'll do some testing to see if this indeed works out ok, but I think we can safely remove the WithEvents keyword from the VB templates (as all event management is done in the base class, regardless of whether the caller is VB or C#)

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 20-Apr-2018 11:26:27   

Fixed in next hotfix build (5.3.5, released later today)

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 20-Apr-2018 13:57:54   

Hotfix build of 5.3.5 with this fix is now available.

Please let us know if this also fixes your deserialization issue simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 20-Apr-2018 20:50:24   

Although I expected this to be the outcome, I was hoping for a different result. Removing the WithEvents feature is a breaking change for me. Consider the following:

Invoice has a calculated field called HasAnExpensiveItem where it looks through the InvoiceItems under it and returns true if any of the InvoiceItems have a cost over X. Before today, I would have created a method in my Invoice entity partial class that handled _invoiceItems.ListChanged and in it called OnPropertyChanged on my calculated field. If I am supposed to use AddHander now, where would you suggest I hook in? WithEvents used to take care of the plumbing for me.

BTW, this will fix my serialization problem. Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Apr-2018 10:16:17   

Create an override of OnInitClassMembersComplete() in a partial class, do the same for OnDeserialized() (as you're using binary serialization).

In these overrides call a method in your partial class, e.g. BindInvoiceItemsHandler(). In BindInvoiceItemsHandler, do:

Dim invoiceItems = Me.InvoiceItems ' This triggers the creation of the collection ' now add the AddHandler here to invoiceItems.

that should make sure the handler is bound properly. Deserializing an entity and its collections doesn't call OnInitClassMembersComplete (as it uses a different ctor) so you have to use the OnDeserialized() method too.

Frans Bouma | Lead developer LLBLGen Pro
Conrad
User
Posts: 37
Joined: 11-Jan-2008
# Posted on: 23-Apr-2018 16:23:32   

I created the override of OnInitClassMembersComplete, but I didn't know about the OnDeserialized. Thanks a lot for the tip!

And thanks again for the support.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 23-Apr-2018 18:00:20   

Glad this is solved now!

Frans Bouma | Lead developer LLBLGen Pro