3.5 child lazy load issue

3.5 child lazy load issue

Old forum URL: forums.lhotka.net/forums/t/6207.aspx


Michael posted on Tuesday, January 20, 2009

Hi Rocky et al

This is a different issue to these posts http://forums.lhotka.net/forums/1/23891/ShowThread.aspx.

I have a BB with a BLB child being lazy loaded in the usual way:

static PropertyInfo<JointSizes> JointSizesProperty
    = RegisterProperty(new PropertyInfo<JointSizes>("JointSizes", "Joint sizes"));
public JointSizes JointSizes
{
    get
    {
        if (!FieldManager.FieldExists(JointSizesProperty))
            LoadProperty<JointSizes>(JointSizesProperty, JointSizes.NewJointSizes());

        return GetProperty<JointSizes>(JointSizesProperty);
    }
}


In a unit test I have created a new parent object and saved it without touching the JointSizes property. Then I attempt to get the JointSizes from the saved object.
FieldManager.FieldExists(JointSizesProperty) returns true, so the LoadProperty is skipped, but GetProperty returns null.

Obviously, this can be solved by touching the property in DataPortal_Create or in DataPortal_Insert using:

DataPortal.UpdateChild(JointSizes, this);
instead of
DataPortal.UpdateChild(ReadProperty<JointSizes>(JointSizesProperty), this);

However, that defeats the point of lazy loading.

The ReadProperty call is loading the field with propertyInfo.DefaultValue, which in this case I do not want to set, so it ends up null. I think FieldManager.LoadFieldData should only be called if propertyInfo.DefaultValue is not null. Very simple fix, but will it break anything else?

If this has been addressed in 3.6, please let me know.

Regards
Michael

ajj3085 replied on Tuesday, January 20, 2009

Are you sure it's not the same issue?  Did you try adding if ( FieldManager.FieldExists( JointSizesProperty ) ) before calling your UpdateChild method?

Michael replied on Tuesday, January 20, 2009

Thanks for your reply. It's definitely not the same issue.

Checking if the field exists before calling update child would avoid the problem, but I stand by my view that loading null should not be the default behaviour. There is no point to lazy loading with a default value of null - there's nothing to load.

Mike

RockfordLhotka replied on Tuesday, January 20, 2009

Clearly some code is causing the creation of the FieldData object. Which means there's an unprotected call to one of the read/get/load/set property methods against that field - probably in your insert/update data code.

By unprotected I mean that it needs to be wrapped by a check for FieldExists.

We can't blindly assume that no application will ever want a null value to be valid for one of these fields. You may never allow a null child reference in your app, but that doesn't mean other people share that view or have the same requirements.

Therefore CSLA can't make blanket assumptions that a null default indicates that the field should never be initialized.

For that matter, there's code in UndoableBase to deal with the possibility that the value could be null, so when you undo it goes back to being null. This code exists because someone, years ago, required a null in a child property, and needed it to work properly with undo.

Michael replied on Tuesday, January 20, 2009

Thanks Rocky

That makes sense. Incidentally, I followed the same pattern as ProjectTracker's Project class with the ProjectResources lazy load, which does not check if the field exists in DataPortal_Insert or DataPortal_Update, so the same problem would exist there (ProjectResources could possibly end up null and there's no way to change it once that happens - get property only). It might be a good idea to update the ProjectTracker example.

Mike

rasupit replied on Wednesday, January 21, 2009

Just to add what Mike has stated.  The following test would fail when trying access the Resouces property:

            var p = Project.NewProject();
            p.Name = "Project Name";
            Assert.IsTrue(p.IsValid);
            try
            {
                p = p.Save();

                Assert.AreEqual(id, p.Id);
                Assert.AreEqual("Project Name", p.Name);
                Assert.AreEqual(0, p.Resources.Count);   <-- null reference here

However, using FieldManager.UpdateChildren(this);  will work just fine.  So the conclusion is that using DataPortal.UpdateChild(ReadProperty(ResourcesProperty), this); on "lazy create property" will mess up the FieldExists test.  To me, that's unexpected.

Ricky

RockfordLhotka replied on Wednesday, January 21, 2009

I realize it is a little confusing. But it is not the UpdateChild() method that’s the issue. It is the ReadProperty() method.

 

So I ask you this:

 

If you have a case where a child reference CAN BE A NULL, and if I make ReadProperty() not create the child, what happens?

 

Does ReadProperty() throw an exception? Does it return null even though the child is not null (because it is not there)? Do I create some new generic type so it is possible for ReadProperty() to return a “PlaceHolder<T>” or something?

 

I hope you see my dilemma. You guys apparently don’t ever want/need a null child. That’s fine. But can you really speak for the entire user base of CSLA now and forever in saying that?

 

Or maybe we decide, here and now, that no one can/will ever have a null child reference. In which case I want several people on the record, so I can point future unhappy people to this thread ;)

 

Or maybe I’m missing some other answer.

 

Maybe the answer is for PropertyInfo<T> to have a “lazy load flag”. You’d set that in your property definition. Then ReadProperty() and GetProperty() would throw an exception if you tried to access them prior to calling LoadProperty() or SetProperty(). That would prevent accidental initialization of the property, without introducing ambiguous behavior.

 

My point is, and I think I’m right, that ReadProperty() can not return null here – because the value IS NOT NULL. It simply doesn’t exist, which is not the same thing.

 

Rocky

 

 

From: rasupit [mailto:cslanet@lhotka.net]
Sent: Wednesday, January 21, 2009 7:13 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] 3.5 child lazy load issue

 

Just to add what Mike has stated.  The following test would fail when trying access the Resouces property:

            var p = Project.NewProject();
            p.Name = "Project Name";
            Assert.IsTrue(p.IsValid);
            try
            {
                p = p.Save();

                Assert.AreEqual(id, p.Id);
                Assert.AreEqual("Project Name", p.Name);
                 Assert.AreEqual(0, p.Resources.Count);   <-- null reference here

However, using FieldManager.UpdateChildren(this);  will work just fine.  So the conclusion is that using DataPortal.UpdateChild(ReadProperty(ResourcesProperty), this); on "lazy create property" will mess up the FieldExists test.  To me, that's unexpected.

Ricky


Michael replied on Wednesday, January 21, 2009

I can see the dilemma. In my case, I did not not want to set a default value at all, but the default is null, so I think a "lazy load/can not be null/no default value" flag is a good idea. Then the call to DataPortal.UpdateChild would behave as expected for null and non-null child scenarios.

Regards
Mike

RockfordLhotka replied on Wednesday, January 21, 2009

Wish list:

 

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=303

 

 

From: Michael [mailto:cslanet@lhotka.net]
Sent: Wednesday, January 21, 2009 5:37 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: 3.5 child lazy load issue

 

I can see the dilemma. In my case, I did not not want to set a default value at all, but the default is null, so I think a "lazy load/can not be null/no default value" flag is a good idea. Then the call to DataPortal.UpdateChild would behave as expected for null and non-null child scenarios.

Regards
Mike



rasupit replied on Wednesday, January 21, 2009

I think is fine to return null (default value) when ReadProperty could not find any value.  I believe DataPortal.UpdateChild would not process when child object is null.  However the issue is FieldManager.FieldExists will return true _after_ the first call of ReadProperty.

I track down the code that causing an insert of FieldData with null value to _fieldData[].  I'm sure there were a good reason for call the LoadFieldData, however this create the problem with FieldExists.

    protected P ReadProperty<P>(PropertyInfo<P> propertyInfo)
    {
      P result = default(P);
      FieldManager.IFieldData data = FieldManager.GetFieldData(propertyInfo);
      if (data != null)
      {
        FieldManager.IFieldData<P> fd = data as FieldManager.IFieldData<P>;
        if (fd != null)
          result = fd.Value;
        else
          result = (P)data.Value;
      }
      else
      {
        result = propertyInfo.DefaultValue;
        FieldManager.LoadFieldData<P>(propertyInfo, result);   <--- this function calls GetOrCreateFieldData internally
      }
      return result;
    }

Ricky
<rant to self>
Not being able to post or have email access at work is a pain in the $$$.  It really annoys the heck out of me know..... I think I should start look for place that don't me you feel like in prison...;(
</rant to self>

ajj3085 replied on Thursday, January 22, 2009

rasupit:
I think is fine to return null (default value) when ReadProperty could not find any value.


I don't.  It doesn't make any sense most of the time, and I think it would cause problems with databinding.  If something asks for the collection, I think it's reasonable to assume you get the collection... not null.

RockfordLhotka replied on Thursday, January 22, 2009

In terms of absolute correctness, the right answer is for ReadProperty/GetProperty to throw an exception if the value hasn’t been initialized (for a lazy loaded property). There is no valid default for something that hasn’t been initialized, and trying to access the uninitialized value is basically a bug.

 

Rocky

 

 

From: ajj3085 [mailto:cslanet@lhotka.net]
Sent: Thursday, January 22, 2009 7:32 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: 3.5 child lazy load issue

 

Image removed by sender.rasupit:

I think is fine to return null (default value) when ReadProperty could not find any value.



I don't.  It doesn't make any sense most of the time, and I think it would cause problems with databinding.  If something asks for the collection, I think it's reasonable to assume you get the collection... not null.


ajj3085 replied on Thursday, January 22, 2009

Hmmm.. that is a good point.  Perhaps that is the correct solution.

Copyright (c) Marimer LLC