Best Practices - Loading a non Lazy Loaded child object.

Best Practices - Loading a non Lazy Loaded child object.

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


bniemyjski posted on Monday, March 16, 2009

Hello,

I have a BO with some child objects like this:

private static readonly PropertyInfo< Category > _categoryProperty = RegisterProperty< Category >(p => p.Category);
        public Category Category
        {
            get
            {
                return GetProperty(_categoryProperty);
            }
        }

        private static readonly PropertyInfo< ItemList > _itemsProperty = RegisterProperty<ItemList>(p => p.Items);
        public ItemList Items
        {
            get
            {
                return GetProperty(_itemsProperty);
            }
        }

I created a fetch helper method like so:

private void Fetch(SafeDataReader reader)
        {
            LoadProperty(_productIdProperty, reader.GetString("ProductId"));
            LoadProperty(_nameProperty, reader.GetString("Name"));
            LoadProperty(_descnProperty, reader.GetString("Descn"));
            LoadProperty(_imageProperty, reader.GetString("Image"));

            LoadProperty(_categoryIdProperty, reader.GetString("CategoryId"));
            LoadProperty(_categoryProperty, Category.NewCategory());

            LoadProperty(_itemsProperty, ItemList.NewList());
        }

I'm looking into what the best way to manually load the Category and/or Items, when I'm not lazy loading the child objects.

Change this

            Product product = Product.GetProduct("BG-03");
            Category cat = Category.GetCategory(product.CategoryId);

Into:

Product product = Product.GetProduct("BG-03");
product.Category = Category.GetCategory(product.CategoryId);

I found a previous post by you saying: "One thing - a child property should never have a set block, only a get block. But I don't see how that would be your problem." I looked over the examples but can't find any information on this.

Also when I do something like this with lazy loading not on, I get an exception.

Item newItem = Item.NewItem();
            newItem.ItemId = "EST-86";
            newItem.ProductId = product.ProductId;
            newItem.ListPrice = 5;
            newItem.UnitCost = 1;
            newItem.SuppId = 1;
            newItem.Name = "New Item that costs 5$";
            newItem = newItem.Save();  // WORKS

            product.Items.Add(newItem);

            Console.WriteLine("");
            Console.WriteLine("Saving");
            product = product.Save(); // Throws:  "Object is not valid and can not be saved"

Stack Trace:

   at Csla.BusinessBase`1.Save() in C:\Users\Administrator\Desktop\CSLA3.6.1\Source\CSLA\BusinessBase.cs:line 122
   at PetShop.UI.Program.Main(String[] args) in C:\Users\Administrator\Documents\CodeSmith\Templates\PetShop\PetShop.UI\Program.cs:line 41
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

      if (!IsValid && !IsDeleted)
        throw new Validation.ValidationException(Resources.NoSaveInvalidException);

Do I have to do something special here? I did find this on a Google search but it didn't help. The rules should be satisfied as I checked and both the required properties are populated and valid.

Thanks
-Blake Niemyjski

JoeFallon1 replied on Monday, March 16, 2009

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

bniemyjski replied on Monday, March 16, 2009

Hello,

This still threw an exception when I tried that..

Thanks
-Blake Niemyjski

RockfordLhotka replied on Monday, March 16, 2009

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().

bniemyjski replied on Tuesday, March 17, 2009

Hello,

Thanks for the information, onto my first question:

I'm looking into what the best way to manually load the Category and/or Items, when I'm not lazy loading the child objects.

Change this

            Product product = Product.GetProduct("BG-03");
            Category cat = Category.GetCategory(product.CategoryId);

Into:

Product product = Product.GetProduct("BG-03");
product.Category = Category.GetCategory(product.CategoryId);

Thank you,
Blake Niemyjski

bniemyjski replied on Thursday, March 19, 2009

Hello,

Unless I am missing something this seems so trivial......

I'm looking into what the best way to manually load the Category and/or Items, when I'm not lazy loading the child objects.

Change this

            Product product = Product.GetProduct("BG-03");
            Category cat = Category.GetCategory(product.CategoryId);

Into:

Product product = Product.GetProduct("BG-03");
????

I want to load the read only property... I know it needs to be loaded by CategoryId. I just need to know how the CSLA community would go about loading this property up....

Thank you,
Blake Niemyjski

RockfordLhotka replied on Thursday, March 19, 2009

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

shawndewet replied on Tuesday, March 23, 2010

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.

Marjon1 replied on Tuesday, March 23, 2010

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.

shawndewet replied on Tuesday, March 23, 2010

Hi Marjon,

It would be great to see the code you are using for this.

Thanks,

Shawn.

Marjon1 replied on Tuesday, March 23, 2010

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