SubEntity null or default: Different with PrefetchPath

Posts   
 
    
AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 18-May-2012 13:23:55   

Hello,

I have added prefetching to existing working code and then noticed that it failed because the OrgUnit entity is now null. So this is a Foreign Key which can be null. Removing the prefetching for the OrgUnit (line 5), will make the code working returning an empty string for the Name.

Some details I found during debugging: _orgUnitReturnsNewIfNotFound is true. _alreadyFetchedOrgUnit will be set to true in SetRelatedEntityProperty where entity is null, when using prefetch SetRelatedEntityProperty is being called twice, when OrgUnit is not null it's first set to null and later to the actual not-null value.

My question is how can I make sure that adding a prefetch does not break existing code?

Kind regards, Alexander

Example code:

string orgUnitName; VUserCollection vUserCollection = new VUserCollection(); IPrefetchPath prefetchPath = new PrefetchPath((int)EntityType.VUserEntity); IPrefetchPathElement subPrefetch = prefetchPath.Add(VUserEntity.PrefetchPathUser); subPrefetch.SubPath.Add(UserEntity.PrefetchPathOrgUnit); vUserCollection.GetMulti(VUserFields.Name == "test", prefetchPath); foreach (VUserEntity userRight in vUserCollection) { orgUnitName = userRight.User.OrgUnit.Name; }

AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 18-May-2012 13:25:37   

Btw Using the 3.1 Final from February 22nd, 2012

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 18-May-2012 16:06:40   

Just check if OrgUnit is not null before accessing its properties.

AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 18-May-2012 16:25:17   

Fixing is not the problem, and already done before posting this message. It's more principal, if you have working code probably running in production for some time, you don't want it to fail if you make it more efficient. If the Foreign Key is only null in rare cases this might become a production bug. It means that after adding Prefetching, you have to check for usages of child entities and adding if constricts everywhere you use this.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 18-May-2012 21:41:32   

<Field>ReturnsNewIfNotFound property is used for lazy-loading fetches. Actually the setting on LLBLGen Designer is called "Lazy loading without results return new". You fetched this with a prefetchPath so it's not lazy loading.

Let's imagine an hypothetical test case. First you eliminate the customer reference from an order and save that. Then you fetch that order again with a Customer prefetch path:

// setup the test. Set the customerId to NULL
var order = new OrderEntity(10248);
order.CustomerId = null;
order.Save();

// fetch the order again with a path
// .CustomerReturnsNewIfNotFound is set to true by default
var path = new PrefetchPath(EntityType.OrderEntity);
path.Add(OrderEntity.PrefetchPathCustomer);
var order2 = new OrderEntity(10248, path);

Now imagine you use this order2 instance somewhere else, and that "somewhere" code doesn't know how you fetched the entity...

var company = order2.Customer.CompanyName;

If both lazy-loading and prefetch-style fetches would return "new" instances for Customer if it's not found, then it's not clear what is the real meaning of the "company" variable. I mean, you might get string.Empty but you are not sure whether this value is from a new entity or the empty value of the DB value. This is more meaningful:

string company = null;
// it was prefetched but it was not found on DB
if (order2.Customer == null)
{
     // ...
}
// it is not null, so it might have a valid value
else
{
     // lazy loading tried to fetch Customer but it wasn't found
     if (order2.Customer.IsNew)
     {
          // ... 
     }

     // it's a valid instance and it was fetched from DB (either by prefetchPath or by lazy-loading
     else
     {
           company = order2.Customer.CompanyName;
     }
}

return company;

Above code was quite long, but it was just to make the point. I do this often in parts of code that receive an entity and don't know much about the entity passed:

public void DoSomething(OrderEntity order2)
{
     if (order2 != null && !order2.IsNew)
    {
        // here, I'm in a safe scope, I know the entity actually exists on DB and it is fetched successfully.
    }
}

So, it's wise to do those checks no matter if you are using PrefetchPaths or just lazy-loading fetches. If those checks would cause extra coding, well that's another story, maybe it's unavoidable. If you use the Fields mapped on related entity feature, you can put a field User.OrgUnitName and llblgen will do the null test for you, so it will return string.Empty if the OrgUnit is null, otherwise the property will return the actual value.

David Elizondo | LLBLGen Support Team
AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 19-May-2012 10:39:15   

Hello daelmo,

Your code example is good and I agree it should be done that way. The only thing I don't understand is:

If both lazy-loading and prefetch-style fetches would return "new" instances for Customer if it's not found, then it's not clear what is the real meaning of the "company" variable. I mean, you might get string.Empty but you are not sure whether this value is from a new entity or the empty value of the DB value.

You're right it's not clear what an empty string means, but this also the case in Lazy loading. If prefetch acts the same I don't see what extra problems this might cause.

My problem is that the project I'm working on is using LllblGen for about 4 years and they used Lazy loading everywhere and only in exceptional cases used prefetching. The programmers got used to the fact that entities exist even if they are not present in the database. So the code will go through child entities without any checks. Performance is now an issue and lazy loading (in loops) is one of the main causes. So we're adding prefetching and running into this issue.

Would a feature request for "<Field>ReturnsNewIfNotFoundWhenPrefetched" be a possible solution?

Regards Alexander.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-May-2012 21:12:03   

Which runitme library version (build number) are you using?

AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 22-May-2012 11:32:11   

Not sure which file I should take, so a few: LlblGenPro.exe 3.1.0.0 SD.LLBLGen.Pro.DQE.OracleODPNet.NET20.dll 3.1.12.222 SD.LLBLGen.Pro.LinqSupportClasses.NET35.dll 3.1.12.216

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 22-May-2012 20:03:38   

As David has explained, te option to return new entity if not fond is only available for lazy loading. And there is a reason behind that.

Lazy loading is invoked by a user action, for which entities are fetched automatically by LLBLGen's runtime, and the developer has no control over it, and can't check for null values.

On the other hand prefetches are invoked by the code the developer writes, and for that he should check for null too if needed.

I understand your situation where old code using Lazy Loading works fine, but at the same time, the developer switching the code to use prefetchPaths, should also take into consideration the check for nulls.

AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 23-May-2012 09:03:24   

I don't understand the distinction you make between Lazy loading and Prefetching: Lazy loading is a user action with no control and prefetching is code the developer writes. The way I look at it is that in both cases it is exactly the same line(s) of code the developer writes, if you don't have to check (default enitity)

company = order2.Customer.CompanyName;

or if you have to check (default null)

if (order2 != null && !order2.IsNew)
{
    company = order2.Customer.CompanyName;
}

What do you mean by Lazy loading is a user action with no control for the developer? In my case I have all control, or at least always the possibility to check for null values. Is there some oher usage scenario of LlblGen then the one I'm doing?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-May-2012 10:34:27   

prefetch paths are always executed by code written by the developer. Lazy loading can also be triggered by databinding code. so you know for 100% of the cases when a prefetch has ran (you wrote it) but in the databinding case, it doesn't have to be the case that lazy loading was triggered.

The option in the framework to return a new entity if lazy loading resulted in no data is one which was added for people who write code which depends on lazy loading being used when accessing a property:

var customer = myOrder.Customer; var companyName = customer.CompanyName;

here, lazy loading will fetch the 'Customer' or if it has already been ran, it will return what was fetched before. For the developer it's transparent whether the query runs or not, he simply expects an entity to be returned.

With prefetch paths that's not the case. The code above will typically fail in Adapter, as it doesn't have lazy loading: by default myOrder.Customer is always null, unless you fetched the customer, either manually or using a prefetch path. So by definition, because it is null by default (!), you always have to check for null.

In the case of lazy loading using framework, like with SelfServicing, you also have to check, but not for null, but whether the entity returned is empty / new or not.

It comes down to the same thing: you always have to check whether the data you expect to be there is actually there. Just because we return a new entity object in the case of lazy loading, doesn't free you from that check: if no data was loaded, it's a new entity object, which might not have any meaning in the code being executed.

If I understand you correctly, your problem is that the code you're working with simply doesn't test for this. However, in selfservicing, whether you use prefetch paths or not, a new entity is always returned if you set the setting to make it do so (I think it's true by default). So what the actually problem is we have to solve for you is unclear to me.

Frans Bouma | Lead developer LLBLGen Pro
AlexanderM
User
Posts: 41
Joined: 18-May-2012
# Posted on: 25-May-2012 16:53:27   

Hello Otis,

Thanks for the answer and the explanation. Unfortunately I don't understand what you say at the end.

However, in selfservicing, whether you use prefetch paths or not, a new entity is always returned if you set the setting to make it do so (I think it's true by default).

I'm using SelfServicing and if I prefetch the subentity, it will be null. The setting is only for lazy loading, see the answer of Daelmo. And that is where my problem starts, adding prefetching might throw exceptions, if you miss where sub entities might be used for example in other helper functions.

Regards, Alexander.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 26-May-2012 11:29:30   

My bad, I should have looked more closely to the actual code and what's been going on, and what I said wasn't true. flushed

When you fetch the related entity with a prefetch path, it sets the flag that the entity is already fetched. If there's no related entity found, it still does that, setting the related entity to null.

When you then read the property, it will first check whether the flag whether the fetch already happened (it did, so it has the value true) and if true, it will skip lazy loading altogether and simply return the value of the property (which is null). If it's false (which is the case when no prefetch path fetch took place and you read the property for the first time), it will perform the lazy loading procedure which will simply instantiate an instance of the related entity type with the value of the FK field(s) which will either read the data into the entity instance or leave it new (if nothing's there). If the flag 'returns new if not found' for that property is true, the entity instance will be returned, otherwise null will be returned.

The flag in question which controls the 'return null if not found' is for lazy loading. It was a design choice in the very first release to return a new entity regardless whether a related entity was there or not to avoid null ref exceptions, however this turned out to be a mistake (I still regret it to this day), and we switched it off (so it returns null properly, like it should) however that would break lots of code, so we added a setting for this: the flags which control the returns new if not found which are controllable from the designer's code generation settings.

So I now see what your problem is: you have to work with code which always assumes there's an entity, however this goes wrong when you add prefetch paths, as a non-found entity fetch then returns null, not a new entity.

It's a problematic situation, because changing this is a breaking change so we won't do that. However it doesn't solve your problem as well. I don't know what the situation is with the code you have to work with, whether there's a way to find the situations where this could cause a problem or not.

To me the situation to return null or a new entity isn't really that different: if I do: var c = myOrder.Customer;

I have to check whether c is a real entity instance: either by testing for null, or by checking whether it's new / entity state is new. If your code doesn't do that either, it's buggy either way. Could you check that please, if the checks for Isnew are there? If so, you can find them and alter them to test for null as well.

Frans Bouma | Lead developer LLBLGen Pro