relationship between foreign keys and related property

Posts   
 
    
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 13-Apr-2007 18:19:35   

I'm starting to realize I don't really understand the relationship between the entities foreign key fields and the object properties they correspond to. For example, why is it that when I do: Entity.ForeignKey_Id = somevalue

that the Entity.ForeignKeyObject doesn't desync itself from the entity? It just sets the internal _foreignKeyObject to 'Nothing' and unsubscribes from events, leaving Entity in its related collection. However, setting the Entity.ForeignKeyObject property to a value will desync the old ForeignKeyObject (removing Entity from its collection)...?

Is it that we aren't supposed to mix the use of setting ForeignKey_ID and ForeignKeyObject? Just use one or the other?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 14-Apr-2007 04:19:47   

Hi Mike, I paste some information from LLBLGenPro Help (Using generated coce - Adapter - Using the entity classes - Modifying an entity - FK-PK synchronization):

Foreign key synchronization with a related Primary key field is done automatically in code.

For example:

Instantiate a Customer entity, add a new Order object to its Orders collection. Now add OrderDetails objects to the new Order object's OrderDetails collection,. You can simply save the Customer entity and all included new/'dirty' entities will be saved and any PK-FK relations will be updated/synchronized automatically. Alter the Customer object in the example above, and save the Order object. The Customer object is saved first, then the Order and then the OrderDetails objects with all PK-FK values being synced This synchronization of FK-PK values is already done at the moment you set a property to a reference of an entity object, for example myOrder.Customer = myCustomer, if the entity (in this case myCustomer) is not new, or if the PK field(s) aren't sequenced fields when the entity is new. Synchronization is also performed after a save action, so identity/sequenced columns are also synchronized.

If you set a foreign key field (for example Order.CustomerID) to a new value, the referenced entity by the foreign key (relation) the field is part of will be dereferenced and the field mapped onto that relation is set to null (C#) or Nothing (VB.NET). Example:

// C#
OrderEntity myOrder = new OrderEntity();
CustomerEntity myCustomer = new CustomerEntity("CHOPS");
adapter.FetchEntity(myCustomer);
myOrder.Customer = myCustomer;  // A
myOrder.CustomerID = "BLONP";   // B
CustomerEntity referencedCustomer = myOrder.Customer; // C
' VB.NET
Dim myOrder As New OrderEntity()
Dim myCustomer As New CustomerEntity("CHOPS")
adapter.FetchEntity(myCustomer)
myOrder.Customer = myCustomer   ' A
myOrder.CustomerID = "BLONP"    ' B
Dim referencedCustomer As CustomerEntity = myOrder.Customer ' C

After line 'A', myOrder.CustomerID will be set to "CHOPS", because of the synchronization between the PK of Customer and the FK of Order. At line 'B', the foreign key field CustomerID of Order is changed to a new value, "BLONP". Because the FK field changes, the referenced entity through that FK field, Customer, is dereferenced and myOrder.Customer will return null/Nothing. Because there is no current referenced customer entity, the variable referencedCustomer will be set to null / Nothing at line 'C'.

The opposite is also true: if you set the property which represents a related entity to null (Nothing), the FK field(s) forming this relation will be set to null as well, as shown in the following example:

// C#
PrefetchPath2 path = new PrefetchPath2((int)EntityType.OrderEntity);
path.Add(OrderEntity.PrefetchPathCustomer);
OrderEntity myOrder = new OrderEntity(10254);
adapter.FetchEntity(myOrder, path);     // A
myOrder.Customer = null;    // B
' VB.NET
Dim path As New PrefetchPath2((int)EntityType.OrderEntity)
path.Add(OrderEntity.PrefetchPathCustomer)
Dim myOrder As New OrderEntity(10254)
adapter.FetchEntity(myOrder, path)      ' A
myOrder.Customer = Nothing      'B

At line A, the prefetch path loads the related Customer entity together with the Order entity 10254. At line B, this customer is dereferenced. This means that the FK field of order creating this relation, myOrder.CustomerId, will be set to null (Nothing). So if myOrder is saved after this, NULL will be saved in the field Order.CustomerId

Above information helps to clarify your answer?

David Elizondo | LLBLGen Support Team
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 14-Apr-2007 18:20:01   

Thanks for the answer, that clears the picture a bit simple_smile

However, I don't understand why you can do something like


Dim employee as new EmployeeEntity()
Dim manager1 as new ManagerEntity()
Dim manager2 as new ManagerEntity()

employee.Manager = manager1
employee.Manager_ID = "" 'assuming Manager_ID is a string
employee.Manager = manager2

And now, the employee will exist in both manager1.Employee collection and also in manager2.Employee collection...it will be in both collections because the line


employee.Manager_ID = "" 'assuming Manager_ID is a string

wouldn't make manager1 remove employee from its collection of EmployeeEntities...the internal code will set employee._manager to Null, but it won't DesetupSync.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 14-Apr-2007 19:03:43   

mikeg22 wrote:


Dim employee as new EmployeeEntity()
Dim manager1 as new ManagerEntity()
Dim manager2 as new ManagerEntity()

employee.Manager = manager1
employee.Manager_ID = "" 'assuming Manager_ID is a string
employee.Manager = manager2

And now, the employee will exist in both manager1.Employee collection and also in manager2.Employee collection...it will be in both collections because the line


employee.Manager_ID = "" 'assuming Manager_ID is a string

wouldn't make manager1 remove employee from its collection of EmployeeEntities...the internal code will set employee._manager to Null, but it won't DesetupSync.

In fact, my test reveals the opposite. The employee is removed from the Employee EntityCollection of manager1. Have you questions about DesetupSync and base.PerformDesetupSyncRelatedEntity methods? and what LLBLGenPro version are you using? and template set (adapter/SS)?

David Elizondo | LLBLGen Support Team
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 24-Apr-2007 08:14:51   

daelmo wrote:

mikeg22 wrote:


Dim employee as new EmployeeEntity()
Dim manager1 as new ManagerEntity()
Dim manager2 as new ManagerEntity()

employee.Manager = manager1
employee.Manager_ID = "" 'assuming Manager_ID is a string
employee.Manager = manager2

And now, the employee will exist in both manager1.Employee collection and also in manager2.Employee collection...it will be in both collections because the line


employee.Manager_ID = "" 'assuming Manager_ID is a string

wouldn't make manager1 remove employee from its collection of EmployeeEntities...the internal code will set employee._manager to Null, but it won't DesetupSync.

In fact, my test reveals the opposite. The employee is removed from the Employee EntityCollection of manager1. Have you questions about DesetupSync and base.PerformDesetupSyncRelatedEntity methods? and what LLBLGenPro version are you using? and template set (adapter/SS)?

I'm using 1.0.2005.1 (July 6).

I just tested this using the AdventureWorks database with the standard Adapter templates.


        Dim daa As New MikesProject.DatabaseSpecific.DataAccessAdapter
        Dim myCollection As New HelperClasses.EntityCollection(New FactoryClasses.EmployeeEntityFactory)
        Dim addresscollection As New HelperClasses.EntityCollection(New FactoryClasses.EmployeeAddressEntityFactory)
        Dim bucket As New RelationPredicateBucket()
        daa.FetchEntityCollection(myCollection, bucket)
        daa.FetchEntityCollection(addresscollection, bucket)

        Dim emp1 As EntityClasses.EmployeeEntity = myCollection(0)
        Dim emp2 As EntityClasses.EmployeeEntity = myCollection(1)
        Dim addr1 As EntityClasses.EmployeeAddressEntity = addresscollection(0)
        Dim addr2 As EntityClasses.EmployeeAddressEntity = addresscollection(1)


        addr1.Employee = emp1
        addr1.EmployeeId = 100
        addr1.Employee = emp2

        MsgBox(emp1.EmployeeAddress.Count)
        MsgBox(emp2.EmployeeAddress.Count)

This outputs "1" "1" indicating that the addr1 entity exists in both emp1 and emp2's collections.

If I remove the "addr1.EmployeeId = 100", it works correctly and the addr1 entity is only in the emp2's collection.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 24-Apr-2007 09:10:48   

Would you please post the runtime library version used? You can request the version of the runtime libraries you're currently using in your code using: string version = SD.LLBLGen.Pro.ORMSupportClasses.RuntimeLibraryVersion.Version + "." + SD.LLBLGen.Pro.ORMSupportClasses.RuntimeLibraryVersion.Build;

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39922
Joined: 17-Aug-2003
# Posted on: 24-Apr-2007 12:49:38   

Wasn't this discussion held recently?

v1.0.2005.1 has different behavior in this than v2.0. the FK field doesn't desync the entity in v1.0.2005.1, while it does in v2.0.

To work around this, set the referenced object to a different value, e.g. null.

In case you're wondering why it's not ported back to v1.0.2005.1: the changes necessary are architecturally intrusive, so changing things in 1.0.2005.1 would mean a breaking change, and we don't do that in a version, only between versions.

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 24-Apr-2007 16:19:27   

Otis wrote:

Wasn't this discussion held recently?

v1.0.2005.1 has different behavior in this than v2.0. the FK field doesn't desync the entity in v1.0.2005.1, while it does in v2.0.

To work around this, set the referenced object to a different value, e.g. null.

In case you're wondering why it's not ported back to v1.0.2005.1: the changes necessary are architecturally intrusive, so changing things in 1.0.2005.1 would mean a breaking change, and we don't do that in a version, only between versions.

I asked before why setting the property to null didn't change the FK to the null equivalent, if thats what you're talking about. confused

What is really bad about this is that to remove the addr1 entity from the emp1.EmployeeAddress collection, I need to set the addr1.Employee property before I can set addr1.EmployeeId. If, for example, I have the addr1.EmployeeId bound to a combobox, there is a serious problem, as many comboboxes don't have a SelectedValueChanging event. Without a way to hook in before the EmployeeId is set, the addr1 entity will not be removed from the original EmployeeEntity's collection. I had to make a custom inherited combobox in order to get around this problem.

I get the reason not to port the fix back, but this really seems like a bug and couldn't there be some kind of "fixed yet not backwards compatible" template set? If not, can you suggest the code to put in the templates?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39922
Joined: 17-Aug-2003
# Posted on: 24-Apr-2007 18:04:49   

Well, it's indeed not something you'd expect. A bug would be if it was advertised but it didn't work. it's something that's not there, but should be there, hence we added it in v2. Adding it in v1.0.2005.1 would break applications, so we didn't do that.

Nittpicking aside: to fix it isn't that easy. The syncing logic isn't trivial as a call-chain often traverses several classes. There's a routine called DesetupSync... in an entity. There, the template traverses all relations and for each relation it emits such a routine. In there, it thus knows which FK fields should be set to a default value, as the fk fields are known in the relation (only interesting for m:1 and 1:1 fk side relations).

You should only reset the FK fields if the call comes from UnsetRelatedEntity. The routine 'SetNewFieldValue()' also calls this routine (if an fk field is set). To prevent infinite loops, you have to NOT set the fk fields in that situation.

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 24-Apr-2007 18:35:33   

Otis wrote:

Well, it's indeed not something you'd expect. A bug would be if it was advertised but it didn't work. it's something that's not there, but should be there, hence we added it in v2. Adding it in v1.0.2005.1 would break applications, so we didn't do that.

Nittpicking aside: to fix it isn't that easy. The syncing logic isn't trivial as a call-chain often traverses several classes. There's a routine called DesetupSync... in an entity. There, the template traverses all relations and for each relation it emits such a routine. In there, it thus knows which FK fields should be set to a default value, as the fk fields are known in the relation (only interesting for m:1 and 1:1 fk side relations).

You should only reset the FK fields if the call comes from UnsetRelatedEntity. The routine 'SetNewFieldValue()' also calls this routine (if an fk field is set). To prevent infinite loops, you have to NOT set the fk fields in that situation.

One last nitpick smile It seems like a bug to me because you can get into a situation where there is an entity in a related entity's collection with a related property set to another owning entity. The bidirectional relationship is broken at this point.

I don't see where there would be an infinite loop if SetNewFieldValue called UnsetRelatedEntity...DesetupSyncX doesn't do anything if the related entity is null, so it won't call back to SetNewFieldValue if it has already run once.

The only problem I see is that it looks like the call order inside SetNewFieldValue would have to be:


If MyBase.SetNewFieldValue(fieldIndex, value, false) Then
    Select case fieldIndex
       Case TheForeignKeyField
            UnsetRelatedEntity(TheForeignKeyObject)
            MyBase.SetNewFieldValue(fieldIndex, value, false)
    End select
End if

making the call to MyBase.SetNewFieldValue twice disappointed

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39922
Joined: 17-Aug-2003
# Posted on: 25-Apr-2007 10:43:31   

mikeg22 wrote:

Otis wrote:

Well, it's indeed not something you'd expect. A bug would be if it was advertised but it didn't work. it's something that's not there, but should be there, hence we added it in v2. Adding it in v1.0.2005.1 would break applications, so we didn't do that.

Nittpicking aside: to fix it isn't that easy. The syncing logic isn't trivial as a call-chain often traverses several classes. There's a routine called DesetupSync... in an entity. There, the template traverses all relations and for each relation it emits such a routine. In there, it thus knows which FK fields should be set to a default value, as the fk fields are known in the relation (only interesting for m:1 and 1:1 fk side relations).

You should only reset the FK fields if the call comes from UnsetRelatedEntity. The routine 'SetNewFieldValue()' also calls this routine (if an fk field is set). To prevent infinite loops, you have to NOT set the fk fields in that situation.

One last nitpick smile It seems like a bug to me because you can get into a situation where there is an entity in a related entity's collection with a related property set to another owning entity. The bidirectional relationship is broken at this point.

No, that's not the case. The FK fields are just not reset at that point, though the opposite reference IS removed.

So if I do: myOrder.Customer = null;

I do get myOrder removed from the customer.Orders collection of the customer referenced by myOrder right before this statement.

There IS a difference indeed with FK field value changes, which result in dereferencing of the related entity which isn't propagated to the other side. This too was a thing we first thought was better, but more people liked the way to have things properly set up, so we changed that in v2. This too was a breaking change so we had to make that in a new version.

I don't see where there would be an infinite loop if SetNewFieldValue called UnsetRelatedEntity...DesetupSyncX doesn't do anything if the related entity is null, so it won't call back to SetNewFieldValue if it has already run once.

Erm... wasn't this about resetting FK fields ? confused So if you do that with SetNewFieldValue(index, null); you get an infinite loop.

The only problem I see is that it looks like the call order inside SetNewFieldValue would have to be:


If MyBase.SetNewFieldValue(fieldIndex, value, false) Then
    Select case fieldIndex
       Case TheForeignKeyField
            UnsetRelatedEntity(TheForeignKeyObject)
            MyBase.SetNewFieldValue(fieldIndex, value, false)
    End select
End if

making the call to MyBase.SetNewFieldValue twice disappointed

Why? If unsetrelatedentity resets an fk field it has to call SetNewFieldValue (if it does it via the property, it is the same).

It's not as simple as it sounds, unless you want to duplicate code, because if you don't mind duplicate code, it's of course not a problem.

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 25-Apr-2007 17:13:28   

Otis wrote:

mikeg22 wrote:

Otis wrote:

Well, it's indeed not something you'd expect. A bug would be if it was advertised but it didn't work. it's something that's not there, but should be there, hence we added it in v2. Adding it in v1.0.2005.1 would break applications, so we didn't do that.

Nittpicking aside: to fix it isn't that easy. The syncing logic isn't trivial as a call-chain often traverses several classes. There's a routine called DesetupSync... in an entity. There, the template traverses all relations and for each relation it emits such a routine. In there, it thus knows which FK fields should be set to a default value, as the fk fields are known in the relation (only interesting for m:1 and 1:1 fk side relations).

You should only reset the FK fields if the call comes from UnsetRelatedEntity. The routine 'SetNewFieldValue()' also calls this routine (if an fk field is set). To prevent infinite loops, you have to NOT set the fk fields in that situation.

One last nitpick smile It seems like a bug to me because you can get into a situation where there is an entity in a related entity's collection with a related property set to another owning entity. The bidirectional relationship is broken at this point.

No, that's not the case. The FK fields are just not reset at that point, though the opposite reference IS removed.

So if I do: myOrder.Customer = null;

I do get myOrder removed from the customer.Orders collection of the customer referenced by myOrder right before this statement.

There IS a difference indeed with FK field value changes, which result in dereferencing of the related entity which isn't propagated to the other side. This too was a thing we first thought was better, but more people liked the way to have things properly set up, so we changed that in v2. This too was a breaking change so we had to make that in a new version.

I guess this leads to my original question in this thread. I don't think I completely understand the theory of how the fk and related entities works in LLBLGen. I can see a system where they are completely disconnected and it is best to just set fk values OR just set related entities (and let the system take care of fk values). I can also see a system where they are tightly connected, where setting an fk will also set a related entity, and setting a related entity will also set an fk. However, LLBLGen seems to be a mix that I'm having trouble understanding. Setting an fk in 1.0.2005.1 will completely get rid of the related entity, but only on one side, leaving the original entity in the other one's collection. Setting a related entity property will sync up the fk-pk, but only if the pk side is not a new entity (would love to be able to turn this off as we always set pks on the client). Adding or removing an entity from a related collection will syncronize the fk-pk values, a mirror image of what happens when you set a related entity property. However, it is possible to have an entity in 2 different collections of 2 different related entities with the fk values only synced to one of those related entities (by doing the example I posted above). This situation, as you said, was first thought to be better, and I'm trying to understand the thinking behind that. simple_smile

I don't see where there would be an infinite loop if SetNewFieldValue called UnsetRelatedEntity...DesetupSyncX doesn't do anything if the related entity is null, so it won't call back to SetNewFieldValue if it has already run once.

Erm... wasn't this about resetting FK fields ? confused So if you do that with SetNewFieldValue(index, null); you get an infinite loop.

Oh, I see what you mean. Because the call to SetNewFieldValue inside DesetupSync happens the line before setting the relatedentity to null. I guess those lines would have to be reversed. cry

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39922
Joined: 17-Aug-2003
# Posted on: 26-Apr-2007 10:40:11   

[quotenick="mikeg22"] [snip]

I guess this leads to my original question in this thread. I don't think I completely understand the theory of how the fk and related entities works in LLBLGen. I can see a system where they are completely disconnected and it is best to just set fk values OR just set related entities (and let the system take care of fk values). I can also see a system where they are tightly connected, where setting an fk will also set a related entity, and setting a related entity will also set an fk. However, LLBLGen seems to be a mix that I'm having trouble understanding. Setting an fk in 1.0.2005.1 will completely get rid of the related entity, but only on one side, leaving the original entity in the other one's collection. Setting a related entity property will sync up the fk-pk, but only if the pk side is not a new entity (would love to be able to turn this off as we always set pks on the client). Adding or removing an entity from a related collection will syncronize the fk-pk values, a mirror image of what happens when you set a related entity property. However, it is possible to have an entity in 2 different collections of 2 different related entities with the fk values only synced to one of those related entities (by doing the example I posted above). This situation, as you said, was first thought to be better, and I'm trying to understand the thinking behind that. simple_smile

Let me say that I agree with you on how it SHOULD work. That's also why I overhauled it in v2 to get it right once and for all. This means that setting an FK field makes the objects to get dereferenced on both sides, setting a reference to a related object to null dereferences the object on both sides and sets the FK fields to null.

In 1.0.2005.1 some gaps are present. I can't deny that, because they're there and you described them correctly. The gaps are part of the philosophy that an FK side object doesn't decide anything in the syncing department, at least in 1.0.200x.y. In v2 we reversed that too: the FK side demands the syncing, the PK side doesn't know anything about that.

In average not much people ran into these gaps though, however it could be a bit cumbersome at times.

I don't see where there would be an infinite loop if SetNewFieldValue called UnsetRelatedEntity...DesetupSyncX doesn't do anything if the related entity is null, so it won't call back to SetNewFieldValue if it has already run once.

Erm... wasn't this about resetting FK fields ? confused So if you do that with SetNewFieldValue(index, null); you get an infinite loop.

Oh, I see what you mean. Because the call to SetNewFieldValue inside DesetupSync happens the line before setting the relatedentity to null. I guess those lines would have to be reversed. cry

I might be mistaken, but I vaguely remember trying that too. You do need to pass what to do to the routine.

Duplicating code is what's the issue here. Take for example Customer and Order. To dereference myOrder from myCustomer you have two options: 1) myCustomer.Orders.Remove(myOrder); or 2) myOrder.Customer = null;

To prevent duplication of code, these two actions call the same routine. So the collection notifies its containing entity myCustomer that entity of type Order is removed. myCustomer then checks what to do and that is to notify myOrder that it has been dereferenced.

In situation 2, the order simply notifies myCustomer that myOrder dereferenced it. myCustomer then simply removes myOrder from its order collection, and thats it, which then results in a situation 1).

resetting FK fields has a similar case: if it is done from the outside: you have to dereference the related entities and thus go for option 2). If you ARE in an option 2) situation, you simply should reset the FK fields and NOT dive into the loop again.

It's this complex because there's no central object like a session which governs all this. This has the advantage that object graphs can be freely moved between tiers etc. however it results in complex code under the hood. It's also complex because part of it is in the runtime lib, part of it is in the templates.

Frans Bouma | Lead developer LLBLGen Pro