Lazy Load not working in 3.5?

Lazy Load not working in 3.5?

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


ifergus posted on Thursday, May 29, 2008

I've got a very large business object with numerous children.  I’m attempting to use lazy loading so that I only need to load the children when necessary.  However, I’m finding that the children are always loaded, even when the parent has no code in its DataPortal_Fetch method to populate them.

Is there something that I need to do to suppress this behavior?  I’m using the note from your March 10th posting as a guideline.

http://www.lhotka.net/weblog/CSLANET35CodeReductionWithChildObjects.aspx

 

RockfordLhotka replied on Thursday, May 29, 2008

Keep in mind that any attempt to read the specified property will trigger the lazy loading. If that property is data bound to any UI element (or a bindingsource) then data binding will read the property as the form is rendered, thus triggering the lazy loading to load immediately.

Or any other code that reads the property, in your object or elsewhere, will trigger the load.

The point being that the technique is great, but only if you are very careful to only read the property if you actually use the data in the property.

ifergus replied on Thursday, May 29, 2008

Thanks Rocky, but I'm not binding it to anything.  In fact, I created a very simple test application and I put a breakpoint on the getter.  The getter was never triggered.

 

RockfordLhotka replied on Thursday, May 29, 2008

Let's take this one step at a time then.

  1. You have NO CODE in the root DP_Fetch() to load the child object?
  2. The only place in your app where you call LoadProperty() or SetProperty() to set the child field is in the property get block of the root object?
  3. You do not have a private backing field for the child property (you are allowing FieldManager to take care of the value)?
  4. You have a DP_Fetch() in the child object to load the child, and an internal/Friend factory on the child class that calls DataPortal.Fetch()?
  5. The only place in all your code that calls that child factory method is the LoadProperty() call in the root object's property get?

If this is all true, then the only way for the child property to end up with a valid value is in that one line of code in the property get. That's the only place the factory is called, which is the only place that can cause the child to load with data.

ifergus replied on Thursday, May 29, 2008

I believe so, but here is the source.  It's pretty simple:

using System;
using System.Collections.Generic;
using Csla;

using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
using System.Data.Common;
using System.Data;

namespace ParentChild
{
    public class Parent : BusinessBase<Parent>
    {
        public static PropertyInfo<int> IdProperty =
            RegisterProperty<int>(new PropertyInfo<int>("Id"));
        public int Id
        {
            get { return GetProperty<int>(IdProperty); }
            set { SetProperty<int>(IdProperty, value); }
        }

        public static PropertyInfo<string> NameProperty =
            RegisterProperty<string>(new PropertyInfo<string>("Name"));
        public string Name
        {
            get { return GetProperty<string>(NameProperty); }
            set { SetProperty<string>(NameProperty, value); }
        }

        public static PropertyInfo<Child> ChildItemProperty =
            RegisterProperty<Child>(new PropertyInfo<Child>("ChildItem"));
        public Child ChildItem
        {
            get
            {
                if (!FieldManager.FieldExists(ChildItemProperty))
                {
                    LoadProperty<Child>(ChildItemProperty, Child.Get(Id));
                }

                return GetProperty<Child>(ChildItemProperty);
            }
            set { SetProperty<Child>(ChildItemProperty, value); }
        }

        public static Parent Get(int Id)
        {
            return DataPortal.Fetch<Parent>(new ById(Id));
        }

       

        private void DataPortal_Fetch(ById criteria)
        {
            SqlDatabase db = new SqlDatabase(ConfigurationManager.ConnectionStrings["Local"].ConnectionString);
            DbCommand cmd = db.GetStoredProcCommand("GetParentById");

            db.AddInParameter(cmd, "id", System.Data.DbType.Int32, criteria.Id);

            using (IDataReader reader = db.ExecuteReader(cmd))
            {
                reader.Read();

                LoadProperty<int>(IdProperty, Convert.ToInt32(reader["Id"]));
                LoadProperty<string>(NameProperty, reader["Name"].ToString());
            }
        }
        [Serializable()]
        private class ById
        {
            private int _id;

            public int Id
            {
                get
                {
                    return _id;
                }
            }
            public ById(int id)
            {
                _id = id;
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
using System.Data.Common;
using System.Data;
using System.Configuration;

namespace ParentChild
{
    public class Child : BusinessBase<Child>
    {
        public static PropertyInfo<int> IdProperty =
            RegisterProperty<int>(new PropertyInfo<int>("Id"));
        public int Id
        {
            get { return GetProperty<int>(IdProperty); }
            set { SetProperty<int>(IdProperty, value); }
        }

        public static PropertyInfo<string> NameProperty =
            RegisterProperty<string>(new PropertyInfo<string>("Name"));
        public string Name
        {
            get { return GetProperty<string>(NameProperty); }
            set { SetProperty<string>(NameProperty, value); }
        }

        public static Child Get(int id)
        {
            return DataPortal.Fetch<Child>(new ById(id));
        }

        private void DataPortal_Fetch(ById criteria)
        {
            SqlDatabase db = new SqlDatabase(ConfigurationManager.ConnectionStrings["Local"].ConnectionString);
            DbCommand cmd = db.GetStoredProcCommand("GetChildById");

            db.AddInParameter(cmd, "id", System.Data.DbType.Int32, criteria.Id);

            using (IDataReader reader = db.ExecuteReader(cmd))
            {
                reader.Read();

                LoadProperty<int>(IdProperty, Convert.ToInt32(reader["Id"]));
                LoadProperty<string>(NameProperty, reader["Name"].ToString());
            }
        }

        [Serializable()]
        private class ById
        {
            private int _id;

            public int Id
            {
                get
                {
                    return _id;
                }
            }
            public ById(int id)
            {
                _id = id;
            }
        }
    }
}

 

RockfordLhotka replied on Thursday, May 29, 2008

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.

So what I'd do is put a breakpoint in your child object's Get() method and look at the stack trace to see how you got there. Very clearly SOME code is calling that method when you don't expect it :)

ifergus replied on Friday, May 30, 2008

Rocky, I don't think that Lazy Load works.

Calling a simple Get on the Parent results in the child being loaded.  I could not get the applicaation to break when calling the Child's Get method.  I do know that immediately after excuting the line:

result = methodHandle.DynamicMethod(obj, inParams);

in MehtodCaller.cs, both parent and child were set.

 

Csla.dll!Csla.Reflection.MethodCaller.CallMethod(object obj = {ParentChild.Parent}, Csla.Reflection.DynamicMethodHandle methodHandle = {Csla.Reflection.DynamicMethodHandle}, object[] parameters = {object[1]}) Line 221 + 0x1c bytes C#

Csla.dll!Csla.Reflection.MethodCaller.CallMethod(object obj = {ParentChild.Parent}, string method = "DataPortal_Fetch", object[] parameters = {object[1]}) Line 149 + 0xe bytes C#

Csla.dll!Csla.Reflection.LateBoundObject.CallMethod(string method = "DataPortal_Fetch", object[] parameters = {object[1]}) Line 86 + 0x17 bytes C#

Csla.dll!Csla.Server.SimpleDataPortal.Fetch(System.Type objectType = {Name = "Parent" FullName = "ParentChild.Parent"}, object criteria = {ParentChild.Parent.ById}, Csla.Server.DataPortalContext context = {Csla.Server.DataPortalContext}) Line 126 + 0x33 bytes C#

Csla.dll!Csla.Server.DataPortal.Fetch(System.Type objectType = {Name = "Parent" FullName = "ParentChild.Parent"}, object criteria = {ParentChild.Parent.ById}, Csla.Server.DataPortalContext context = {Csla.Server.DataPortalContext}) Line 120 + 0x12 bytes C#

Csla.dll!Csla.DataPortalClient.LocalProxy.Fetch(System.Type objectType = {Name = "Parent" FullName = "ParentChild.Parent"}, object criteria = {ParentChild.Parent.ById}, Csla.Server.DataPortalContext context = {Csla.Server.DataPortalContext}) Line 42 + 0x15 bytes C#

Csla.dll!Csla.DataPortal.Fetch(System.Type objectType = {Name = "Parent" FullName = "ParentChild.Parent"}, object criteria = {ParentChild.Parent.ById}) Line 219 + 0x12 bytes C#

Csla.dll!Csla.DataPortal.Fetch<ParentChild.Parent>(object criteria = {ParentChild.Parent.ById}) Line 165 + 0x34 bytes C#

ParentChild.dll!ParentChild.Parent.Get(int Id = 1) Line 50 + 0x23 bytes C#

ParentChildTest.dll!ParentChildTest.UnitTest1.TestMethod2() Line 27 + 0xb bytes C#

RockfordLhotka replied on Friday, May 30, 2008

I just created a test (though I already have other tests too) to firmly establish that a child isn’t created unless some code calls the child’s factory method. That statement is a truism of course J

 

Seriously though, lazy loading works fine. You absolutely have some code that is calling the child’s factory method when you don’t expect it. The easiest way to find the issue is to put a breakpoint in the child factory and then look at the stack trace to see who called it.

 

Here’s my test code:

 

      var p = AParent.GetParent();

      Display.AppendText(string.Format("{0}: {1}", "parent data", p.Data));

      Display.AppendText(Environment.NewLine);

      Display.AppendText(string.Format("{0}: {1}", "child exists", p.ChildExists()));

      Display.AppendText(Environment.NewLine);

      var c = p.GetChild();

      if (c != null)

        Display.AppendText(string.Format("{0}: {1}", "child data", c.Data));

      else

        Display.AppendText(string.Format("{0}: {1}", "child value", "null"));

      Display.AppendText(Environment.NewLine);

 

      c = p.Child;

      Display.AppendText(string.Format("{0}: {1}", "child exists", p.ChildExists()));

      Display.AppendText(Environment.NewLine);

      if (c != null)

        Display.AppendText(string.Format("{0}: {1}", "child data", c.Data));

      else

        Display.AppendText(string.Format("{0}: {1}", "child value", "null"));

      Display.AppendText(Environment.NewLine);

 

Here’s the resulting output:

 

parent data: xyz

child exists: False

child value: null

child exists: True

child data: abc

 

And here are the AParent and AChild classes:

 

  [Serializable]

  public class AParent : BusinessBase<AParent>

  {

    private static PropertyInfo<string> DataProperty =

      RegisterProperty<string>(typeof(AParent), new PropertyInfo<string>("Data", "Data"));

    public string Data

    {

      get { return GetProperty<string>(DataProperty); }

      set { SetProperty<string>(DataProperty, value); }

    }

 

    private static PropertyInfo<AChild> ChildProperty =

      RegisterProperty<AChild>(typeof(AParent), new PropertyInfo<AChild>("Child", "Child"));

    public AChild Child

    {

      get

      {

        if (!FieldManager.FieldExists(ChildProperty) || ReadProperty<AChild>(ChildProperty) == null)

          LoadProperty<AChild>(ChildProperty, AChild.GetChild());

        return GetProperty<AChild>(ChildProperty);

      }

    }

 

    public bool ChildExists()

    {

      return FieldManager.FieldExists(ChildProperty);

    }

 

    public AChild GetChild()

    {

      return GetProperty<AChild>(ChildProperty);

    }

 

    private AParent()

    { }

 

    public static AParent GetParent()

    {

      return DataPortal.Fetch<AParent>();

    }

 

    private void DataPortal_Fetch()

    {

      LoadProperty<string>(DataProperty, "xyz");

    }

  }

 

  [Serializable]

  public class AChild : BusinessBase<AChild>

  {

    private static PropertyInfo<string> DataProperty =

      RegisterProperty<string>(typeof(AChild), new PropertyInfo<string>("Data", "Data"));

    public string Data

    {

      get { return GetProperty<string>(DataProperty); }

      set { SetProperty<string>(DataProperty, value); }

    }

 

    private AChild()

    { }

 

    internal static AChild GetChild()

    {

      return DataPortal.FetchChild<AChild>();

    }

 

    private void Child_Fetch()

    {

      LoadProperty<string>(DataProperty, "abc");

    }

  }

 

Rocky

 

tshah replied on Friday, May 30, 2008

Rocky, Lazy Load did not work. I took both the samples from this thread. In both the cases Calling a simple Get on the Parent results in the child being loaded. Application didn't break when calling the Child's Get method.

I am currently running CSLA .NET 3.5 and wondering if this has something to do with the version.

What version of CSLA are you running?

RockfordLhotka replied on Friday, May 30, 2008

I have suggested this twice now. The only way to resolve this is for you to put a breakpoint in the child’s factory method and look at the stack trace to see what called that method. Perhaps you can do that and post the stack trace result so I can see what is invoking the factory?

 

Rocky

ifergus replied on Friday, May 30, 2008

Rocky,

Lazy Load is working fine.  It turns out that the child was being called by the debugger -- in retrospect -- a very silly mistake on my part

Thanks for all your help and your quick responses.

RockfordLhotka replied on Friday, May 30, 2008

I am glad you figured out what was going on.

 

It wouldn’t be real obvious that the debugger was triggering the property get though – how’d you figure that out?

swegele replied on Wednesday, June 04, 2008

I (to my shame) had the same issue once...I finally realized that QuickWatch window was hitting the property obviously through reflection.  UGH

 

Sean

zinovate replied on Wednesday, June 04, 2008

The QuickWatch totally makes sence. At first I was wondering how that was called....

jfreeman replied on Monday, August 11, 2008

I have a similar situation and wanted to get some input on how to handle it.  I have a parent object call AcceptedData and children called RawData.  I only have one record for the AcceptedData but could have multiple RawData records for each month.  I want to have 2 grids to handle this situation.  The main grid contains all the AcceptedData.  The 2nd grid would show the RawData records for the selected AcceptedData record.  When I set up the objects like Rocky suggested for Lazy Loading my Infragistics grid automatically recognizes the child objects and adds them to the main AcceptedData grid.  Can I do what I described using the normal parent-child relationship or should I just make these independent objects and handle loading the 2nd grid in the UI events?  Thanks.

Jonathan

 

ajj3085 replied on Monday, August 11, 2008

I recently had this come up as well.  The solution is specific to the Infragistics grid:

http://forums.infragistics.com/forums/t/11251.aspx

jfreeman replied on Monday, August 11, 2008

That fixed my grid from showing the child records but the new grid is not being populated when I click on a record in the AcceptedData grid.  How did you handle this?  Do I need an event in the UI or a method in my AcceptedData object?  Thanks.

Jonathan

ajj3085 replied on Monday, August 11, 2008

Hi,

I actually don't have a child grid to display the children; I just wanted them not to display at all.  But you should be able to handle an event from the grid to set the child grid to the appropriate data source.  I think it's something like AfterRowSelect or something like that.

Copyright (c) Marimer LLC