Best practice for 1:1 object relations when fetchResult = False

Posts   
 
    
cardplayer
User
Posts: 24
Joined: 20-Apr-2007
# Posted on: 05-Oct-2008 04:30:38   

Version: 1.0.2005

We have a Parent object with a 1:1 relation with a Child. We load the Parent and subsequently reference the Child, such as this:

dim localChild as ChildEntity = parentReference.Child

This allows us to work with "localChild", but if any subsequent code references parentReference.Child again, the internal "GetSingleChild(False)" in the Parent object will be executed again, and since the member variable _alreadyFetchedChild is False, the child is refetched from the database. Since it does not exist, this causes a new child to be created and passed to the setter for the Child() property. This fires SetupSyncChild(relatedEntity). We note that the Set part of the Child() property calls down into SetupSynchChild(relatedEntity). SetupSyncChild() then sets _alreadyFetchedChild to true. However, the code in GetSingleChild() immediately sets _alreadyFetchedChild = fetchResult. In effect, this results in the following sequence of behavior:

1.) Since _alreadyFetchedChild = False, any reference to the Child() property getter will cause a new child object to be loaded from the store 2.) fetchResult = false in GetSingleChild(), since the child does not exist in the store 3.) GetSingleChild() calls the setter for the Child() property like so: Me.Child = newEntity 4.) The setter then executes this sequence: Dim relatedEntity As IEntity = CType(value, IEntity) relatedEntity.SetRelatedEntity(Me, "Child") SetupSyncChild(relatedEntity)

5.) SetupSyncChild() sets _alreadyFetchedChild to True 6.) Immediately thereafter, GetSingleChild() does this: _alreadyFetchedChild = fetchResult

This causes a nasty side effect in the code, whereby we continue to work with our localChild reference, thinking that it is the same object as Parent.Child, but other code that leverages the object graph in the meantime may reference the Child(), causing it to reload, and now the reference that we hold in localChild() is, in effect, "disconnected" from the Parent, and the Parent now has a new Child, which it will not save to the store!!!!

Questions:

1.) Why must repeated references to Parent.Child() result in the reloading of Child() each and every time, even if we have set properties on Child, and Child is plenty dirty?

2.) We can see in the comments that the Parent.Child() property is for convenience only, and that we should use GetSingleChild(). Why is this so? It seems to exhibit the same behaviors.

3.) Assuming that we do get an object reference back from GetSingleChild() or from Parent.Child(), how can we tell that the internal fetch failed and that we need to assign a new object reference to the Parent.Child property in order to have it stop reloading? What is the best practice here? In our use case, we need to check whether or not the Child has been assigned yet, and if not, create one. However, since the generated code does not return a Null reference, we seem unable to do so.

4.) As a last question, why does the Child not save to the store in cases of fetch failure? There is an object reference there, and it IsNew, and IsDirty, so why is it not being persisted during recursive saves? What prevents it from being saved?

Regards, Cardplayer

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 07-Oct-2008 09:11:52   

Hi Cardplayer. First of all: Do you have the latest 1.0.2005.1 build? (to make sure, go to download section and update to the latest build.

David Elizondo | LLBLGen Support Team
cardplayer
User
Posts: 24
Joined: 20-Apr-2007
# Posted on: 08-Oct-2008 01:27:59   

Thank you for your reply. We are running 1.0.2005.1 for sure.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 09-Oct-2008 07:20:24   

I'm testing this with the 1.20051.7.307 LLBLGen RuntimeLibrary version:

// one query executed
Dim theDetail As OrderDetailEntity = New OrderDetailEntity(10252, 60)

// one query executed
Dim theOrder As OrderEntity = theDetail.Order

// no query executed
Dim orderId As Integer = theDetail.Order.OrderId

// no query executed
Dim orderDate As Date = theDetail.Order.OrderDate

If I understand you ok, you get a lot of fetches when accessing the same child property. Please post your RuntimeLibrary version (http://llblgen.com/TinyForum/Messages.aspx?ThreadID=7718) and generated SQL (LLBLGenPro Manual -> Using generated code -> Troubleshooting and debugging).

David Elizondo | LLBLGen Support Team
cardplayer
User
Posts: 24
Joined: 20-Apr-2007
# Posted on: 09-Oct-2008 22:59:29   

I think I need to clarify just a bit. I will update the thread with the runtime versions as soon as I can access that machine. In the meantime, please consider a reworded example.

Let's say there is an OrderEntity. Related to that OrderEntity is an OrderEntryPersonEntity. The relation is 1:1. In some cases, due to errors in legacy data, OrderEntryPersonEntity might not exist for each OrderEntity. This is key to understanding the issue.

Thus, to load an order, we would use:

'This loads the order Dim order As OrderEntity = New OrderEntity() If Not order.FetchUsingPK(12345) Then Throw New ApplicationException("Order not found.") End If

'Now, we try to access the OrderEntryPersonEntity 'Assume, for this example, that the OrderEntryPersonEntity does not exist <-- This is key

'1 Query executed Dim person as OrderEntryPersonEntity = order.OrderEntryPerson

'At this point, we have person, which should be a reference to order.OrderEntryPerson. 'We will demonstrate that this is the case, unless and until some piece of code uses order.OrderEntryPerson again, at which point the person reference becomes orphaned, in a manner of speaking, because order.OrderEntryPerson is refetched.

Hence:

'If we access order.OrderEntryPerson again, for ANY reason, it reloads 'This is because _alreadyFetchedOrderEntryPerson is False, as described in the long-winded message that started this thread.

'Thus: '1 Query executed Dim personId As Integer = order.OrderEntryPerson.PersonID

'1 Query executed Dim lastName As String = order.OrderEntryPerson.LastName

'1 Query executed Dim address As String = order.OrderEntryPerson.Address

'This goes on and on and on. 'Careful debugging showed the same scenario as the first post

'We are able to stop the behavior in this manner: 'This loads the order Dim order As OrderEntity = New OrderEntity() If Not order.FetchUsingPK(12345) Then Throw New ApplicationException("Order not found.") End If

If order.OrderEntryPerson.IsNew() Then order.OrderEntryPerson = New OrderEntryPersonEntity() End If

After that, we can reference order.OrderEntryPerson to our heart's content, and no additional queries result, because _alreadyFetchedOrderEntryPerson will be True from then on, due to the behavior of the setter.

Thanks, Cardplayer

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 10-Oct-2008 07:59:11   

I get it now. Reproduced with the 1.20051.7.307 build:

' Suppose that the CustomerId for this order is NULL at DB 
' 1 query executed
Dim theOrder As OrderEntity = New OrderEntity(10248)

' 1 query executed
Dim theCustomer As CustomerEntity = theOrder.Customer

' 1 query executed
Dim customerId As String = theOrder.Customer.CustomerId

' 1 query executed
Dim companyName As String = theOrder.Customer.CompanyName

With v2.0 this works as expected (just two fetches). We will see what we can do on this. In the meantime keep using your workaround (reinstantiate the related object).

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 10-Oct-2008 11:07:57   

In v2.0 we fixed this with code which checks the FK values, if they're not set, they're 'invalid' for related entity fetches and lazy loading doesn't occur.

As this is a breaking change we did it in v2.0. We also can't port code back to lower versions if it's a change/additional feature of a later version.

To overcome what you see, you can use the workaround.

Frans Bouma | Lead developer LLBLGen Pro
cardplayer
User
Posts: 24
Joined: 20-Apr-2007
# Posted on: 10-Oct-2008 13:37:10   

Frans and David,

You guys are great. Thanks for the follow-up. LLBLGen Pro is still #1!

cardplayer
User
Posts: 24
Joined: 20-Apr-2007
# Posted on: 10-Oct-2008 13:39:17   

Frans and David,

You guys are great. Thanks for the follow-up. LLBLGen Pro is still #1!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 10-Oct-2008 15:31:49   

simple_smile

Frans Bouma | Lead developer LLBLGen Pro