3.5 child lazy load issue3.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
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