It is not very clear what you are doing.
newItem = newItem.Save(); // WORKS
product.Items.Add(newItem);
product = product.Save(); // Throws: "Object is not valid and can not be saved"
You said newItem is a child BO so why are you calling .Save and updating the reference? That changes it's state.
Just add it to the list and then call product.Save and it should work normally.
Joe
I am a bit confused by what you are trying to do.
It appears, on the surface, like you are trying to put root objects into a BusinessListBase. That is not supported. You can make it work, but it requires some hackery because you are flying the in face of the supported model.
A BLB should contain child objects. Those objects are saved when the BLB is saved, not by themselves.
You can create a child object independently, and then add it to the BLB, but it won't actually get inserted until the BLB is saved (and thus all its child objects are saved).
So your "newItem = newItem.Save()" code is saving a root object - by definition, if the object is savable then it is a root object.
You then add it to a BLB, which would normally require that it be a child object. But you created it as a root, so its IsChild property is false. That violates the rules of a BLB, because a BLB can only contain child objects.
Again, using hackery, you can force it to work. You just need a way to mark the 'newItem' object as a child before adding it to the collection. You'd need to expose some public method on the newItem object that calls MarkAsChild().
I lost track of this thread, and don’t recall the history.
This is a thing, and I don’t understand where there’s
any complexity.
For a name/value list or other read-only list that is filtered
based on data related to a specific data element – like a list of
categories filtered to be appropriate to a specific product object’s
category, I’d have the Product class load the category list itself.
public class Product
{
private static PropertyInfo<CategoryList> CategoryListProperty
= RegisterProperty<CategoryList>(c => c.CategoryList);
public CategoryList CategoryList
{
get { return
GetProperty(CategoryListProperty); }
}
private void DataPortal_Fetch(…)
{
// load product data as normal
LoadProperty(CategoryListProperty,
CategoryList.GetList(this.CategoryId));
}
}
The only drawback is that this round-trips the list with the
product object. To avoid that, lazy-load the list:
public class Product
{
private static PropertyInfo<CategoryList> CategoryListProperty
= RegisterProperty<CategoryList>(c => c.CategoryList);
public CategoryList CategoryList
{
get { return CategoryList.GetList(this.CategoryId);
}
}
private void DataPortal_Fetch(…)
{
// load product data as normal
}
}
This is particularly effective if your CategoryList factory
method includes a bit of caching code to avoid reloading the list from the
database – and since these lists are often quite static, caching is
usually fine.
Rocky
Hi Rocky, I've been trying to figure out the most efficient way to implement lazy-loaded child collection properties, and came across this thread...
From your last post you define a lazy-loaded collection property as follows...
public CategoryList CategoryList { get { return CategoryList.GetList(this.CategoryID)}}
...is the idea here that the GetList method contains a call to DataPortal.FetchChild?
Doing so has the advantage that the DP is flagging objects as children correctly, but the problem is that this call is not checking whether or not it should run locally or via the remote dataportal, and thus this approach would not work where one is using the remote dataportal.
The alternative is to have the GetList method call DataPortal.Fetch (as opposed to .FetchChild), but now one would have to bring in some "hackery" to have the returned collection be flagged as a child.
I'm thinking specifically in a scenario where I have OrderHeader objects, with a property called MyOrderLines. This property of MyOrderLines represents a child collection of editable OrderLine objects, all of which have to be saved from the their OrderHeader's Save method. But I need advice in how to lazy-load this MyOrderLines property, considering the problems with the two approaches outlined in this post.
The way that we are planning to use resolve this problem (have just finished writing the code to start using it in our current and future projects), is that we have created a CommandBase object that is used to call the DataPortal.FetchChild or CreateChild for us so that it does go cross the network boundary if it is required.
Somewhere within the code (either as part of the CategoryList factory or directly in the property) we would make the following call:
LazyLoadCommand<CategoryList>.FetchObject(this.CategoryId);
Apologies if the C# code is wrong, it's not my first language and I didn't verify it in VS.
This makes it nice a clear that the ChildDataPortal methods should be used for all children objects, but can still be used in a 3+ tier environment.
I get the feeling that this is not the standard approach used by the community (probably because there was no child methods in earlier versions) and they just use the DataPortal_Fetch and manage the extra proprieties required.
Should you want the code would be happy to post it.
Hi Marjon,
It would be great to see the code you are using for this.
Thanks,
Shawn.
Shawn,
As requested here is the code to lazy-load an object using Child_DataPortal methods
to ensure that it always cross the network boundary if configured to do.
The code is VB.NET as that is what we use and sorry for the lack of formatting, still haven't manage to master the forum editor!
<Serializable()> _
Public Class LazyLoadCommand(Of T)
Inherits CommandBase
Private _createNew As Boolean = True
Private _parameters As Object()
Private _returnValue As T
Public Shared Function GetObject(ByVal ParamArray parameters() As Object) As T
Dim cmd As New LazyLoadCommand(Of T)(False, parameters)
DataPortal.Execute(cmd)
Return cmd._returnValue
End Function
Public Shared Function NewObject(ByVal ParamArray parameters() As Object) As T
Dim cmd As New LazyLoadCommand(Of T)(True, parameters)
DataPortal.Execute(cmd)
Return cmd._returnValue
End Function
Public Shared Function NewObject() As T
Dim cmd As New LazyLoadCommand(Of T)()
DataPortal.Execute(cmd)
Return cmd._returnValue
End Function
Private Sub New()
'Require the use of a factory method
End Sub
Private Sub New(ByVal CreateNew As Boolean, ByVal ParamArray parameters() As Object)
_createNew = CreateNew
_parameters = parameters
End Sub
Protected Overrides Sub DataPortal_Execute()
If _createNew Then
_returnValue = DataPortal.CreateChild(Of T)(_parameters)
Else
_returnValue = DataPortal.FetchChild(Of T)(_parameters)
End If
End Sub
End Class
Copyright (c) Marimer LLC