CSLA 3.5 Bug - Child Collection Objects Not Updating Correctly After Save.

CSLA 3.5 Bug - Child Collection Objects Not Updating Correctly After Save.

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


tabaguley posted on Saturday, June 28, 2008

I have come across what I believe to be a bug with managed collection properties in child/grand-child collection objects.  I am using CLSA 3.5.0, however, I have updated most of the code for the FieldManager/n-level undo stuff to the latest (3.5.1) posting on SVN.

Here is the basic structure I'm working with:

HardwareDevices (Editable Root List)

   -> HardwareDevice (Editable Child Object)

         -> DeviceProperties (Editable Child Collection)

               -> DeviceProperty (Editable Child Object)

Here is the problem:

When a new DeviceProperty is added to the DeviceProperties collection of a HardwareDevice, everything works normally until HardwareDevices is saved.  After saving, any DeviceProperty objects added in will show up in the DeviceProperties collection; however, when Save is again called on HardwareDevices, none of the HardwareDevice objects report back as being Dirty (and hence, none of the DeviceProperty objects added after the first Save get saved).

I have tried using the CSLA 3.5 Managed Property setup with and without a state variable for DeviceProperties, and even tried the pre-3.5 method of using just a state variable and overriding IsDirty and IsValid.  Each manner of doing things yielded the same results. 

From what I can tell, the internal FieldManager isn't tracking reference-types correctly.  I noticed that SmartDate was changed from a Class(reference-type) to a Structure (value-type).  When upgrading several of my custom validation objects, I also had to convert them to Structures in order to get them to work correctly with the Managed Properties feature of CSLA 3.5.  I don't know if this could be part of the problem or not, but I thought it worth mentioning.

RockfordLhotka replied on Tuesday, July 01, 2008

Are you in a Windows Forms environment? If so, are you unbinding and rebinding the objects to the UI correctly?

It sounds like your UI may still be bound to the old object instance, not the new one returned by the Save() method.

It isn't an object vs struct thing - SmartDate has been a struct for a few years now.

But if you recently switched from 3.0 to 3.5, and you are using a local data portal, you may have missed the fact that the local data portal now acts like a remote data portal in terms of returning an updated object instance, rather than altering your instance in-place. This is discussed in the change log and can be reverted to the old (bad) behavior using the AutoCloneOnUpdate config setting.

tabaguley replied on Monday, July 07, 2008

I am actually upgrading from CSLA 1.1 to 3.5 (finally!), and am finding I have a lot to learn about the new framework.  Yes, I am working in a Windows Forms environment.  With that said, I believe I've figured out the problem. 

It appears that CSLA objects are designed to work (best) with a BindingSource object, which I do not use.  I have played around with the BindingSource object, and find it problematic - it seems to be just an extra ("middle-man") object that isn't necessary the way I do things.  Because I do not use a BindingSource, I believe that child collections contained within a given object do not get initialized properly. 

I do understand how the local data portal works, and I do everything correctly as far as binding, unbinding, and updating references.  However, even though I update the references, the child collection objects end up as Nothing after a Save operation. 

But I have come up with a fix: prior to save, I simply call ToString on each Child Collection in each object (I also override ToString on each Child Collection to simply return the Name & Count of the collection).  This appears to initialize the collection within the FieldManager (which I believe is what te BindingSource object would be doing if I used one), and then everything works fine after the Save. Here's an example:

Public Overrides Function Save() As ParentCollection
    'See if Save is allowed...
    If Not CanEditObject() Then
        Throw New Exception("User not authorized to save Parent Collection.")
    End If

    'Ensure Child Collections Are Initialized...
    For Each child As ChildObject In Me
        child.GrandChildCollection.ToString()

        For Each grandChild As GrandChildObject In child.GrandChildCollection
            grandChild.GreatGrandChildCollection.ToString()
        Next
    Next

    Return MyBase.Save()
End Function

 

RockfordLhotka replied on Monday, July 07, 2008

It is true that starting with .NET 2.0 I only support the use of a BindingSource for data binding. Microsoft used the BindingSource to fix a bunch of bugs and issues around data binding, and I didn’t want to suffer with those 1.x issues myself J

 

If you’ve already got good workarounds for all the .NET 1.x binding issues (and it sounds like you do) that’s good – but it doesn’t surprise me that you would have to do some work in or around CSLA to accommodate the older binding scheme.

 

Rocky

 

van replied on Friday, November 07, 2008

I have a problem with saving collection which associated to object.

If I add new item to collection, collection is dirty but object is not, and DP_Update never invoked.

Before save I must do folowing:

Sednica.DokumentList = null;

Sednica.DokumentList = documentList;

Sednica = Sednica.Save();

documentList is same object as a Sednica.DokumentList

RockfordLhotka replied on Friday, November 07, 2008

If the object isn’t dirty it won’t be saved. You need to ensure that the newly added object is dirty.

 

Rocky

 

 

From: van [mailto:cslanet@lhotka.net]
Sent: Friday, November 07, 2008 7:07 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: CSLA 3.5 Bug - Child Collection Objects Not Updating Correctly After Save.

 

I have a problem with saving collection which associated to object.

If I add new item to collection, collection is dirty but object is not, and DP_Update never invoked.



van replied on Thursday, November 13, 2008

I think you didn't understand me. I have class Sednica, inherited from BaseObject<T> which inherited from BusinessBase<T>. In Sednica I have PrisutnostList. When I load data from database PrisutnostList is not dirty and Sednica is not dirty. That is OK. If I add new item in the PrisutnostList or change some item\items, list is dirty and that is OK. But Sednica is not dirty, and I can't save changed list because I save list in DP_Insert or DP_Update in the Sednica object under transaction. PrisutnostList is not child object. I try with child object and same problem appear.

I try subscribe to event ListChanged and you can see that code under comment. But this is not work because ListChanged event fires several times and I get StackOverflowException.

public class Sednica : BaseObject<Sednica>

{

   private static PropertyInfo<PrisutnostList> PrisutnostListProperty = RegisterProperty<PrisutnostList>(typeof(Sednica), new PropertyInfo<PrisutnostList>("PrisutnostList", "PrisutnostList"));

   private PrisutnostList _prisutnostList;

   public PrisutnostList PrisutnostList

   {

   get

   {

      if (_prisutnostList == null)

      {

      _prisutnostList = PrisutnostList.GetPrisutnostBy(this);

      //if (_prisutnostList != null)

      //{

      // _prisutnostList.ListChanged += _prisutnostList_ListChanged;

      //}

     }

      return GetProperty<PrisutnostList>(PrisutnostListProperty, _prisutnostList);

   }

   set

   {

         SetProperty<PrisutnostList>(PrisutnostListProperty, ref _prisutnostList, value);

         //_prisutnostList.ListChanged += _prisutnostList_ListChanged;

     }

   }

   //void _prisutnostList_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)

   //{

         // MarkDirty();

   //}

}

ajj3085 replied on Thursday, November 13, 2008

 Hmm... you're using private backing fields.  I don't think that will allow your root class to automatically update IsDirty / IsValid.  I suspect you'll need to override like this:

public override bool IsDirty {
   get { return IsSelfDirty || _prisutnostList.IsDirty; }
}

public override bool IsValid {
   get { return base.IsValid && _prisutnostList.IsValid; }
}

RockfordLhotka replied on Thursday, November 13, 2008

Yes, if you use private backing fields for your child objects you MUST use the old style of coding, because CSLA can’t do the work for you.

 

I strongly recommend using managed backing fields for child objects, even if you use private backing fields for primitive types, because this allows CSLA to automatically do all the little, easy-to-forget things you have to do to make a child object work properly.

 

Rocky

 

AaronH replied on Thursday, November 13, 2008

I think I've run into the same issue, but I'm not satisfied with the work-around (it's terribly kludgey).

Please try running the following code in PTracker.  Note that I have made no changes to any of the 3.5.2 PTracker classes.

Project p = Project.NewProject();
p.Name = "Aarons project";
p.Started = DateTime.Now.ToString();
p.Ended = DateTime.Now.AddYears(2).ToString();
//System.Diagnostics.Debug.WriteLine("Resources = null? : " + (p.Resources == null).ToString());
p = p.Save();
p.Resources.Assign(2);
p.Save();

It should throw an exception stating that Resources is null. If you uncomment the Debug.WriteLine statement and run again, it works fine.  I'm confused.

ajj3085 replied on Thursday, November 13, 2008

Sorry, I don't follow.  Prior to managed fields, you HAD to override IsDirty / IsValid for Csla to work.  Using managed fields allows you to not have to implement that code.  Neither are kludgy though.. that's how you use Csla.

I checked out the 3.6 code, and don't see where it would thrown an exception.  On what line is the exception thrown?

AaronH replied on Thursday, November 13, 2008

Please run the code and you'll see what I'm talking about.  When you hit the line that attempts to assign a resource to the project, the resources collection is NULL.  If you uncomment the Debug statement, the resources collection will NOT be NULL when it comes time to assigning the resource to the project.  Does that make sense?  Again, running the code will make everything clear.

Thanks!

ajj3085 replied on Thursday, November 13, 2008

Ok, I ran the code.  My confusion lies in that the bug you're encountering has nothing to do with the solution to the other poster's problem.

I would upgrade to 3.6, because if you run your same code using Csla 3.6, it functions fine.

AaronH replied on Thursday, November 13, 2008

Yeah, I should have been more clear as to which post I was referencing.  Here is the quote that I was referring to:

-----------------------------

tabaguley

I do understand how the local data portal works, and I do everything correctly as far as binding, unbinding, and updating references.  However, even though I update the references, the child collection objects end up as Nothing after a Save operation.

But I have come up with a fix: prior to save, I simply call ToString on each Child Collection in each object (I also override ToString on each Child Collection to simply return the Name & Count of the collection).  This appears to initialize the collection within the FieldManager (which I believe is what te BindingSource object would be doing if I used one), and then everything works fine after the Save. Here's an example:
 
------------------------------

Now, no offense to the original author, but if that's not a kludgey workaround, I don't know what is ;)

van replied on Friday, November 14, 2008

Thank you Rocky. Problem is solved using managed field.

van replied on Thursday, November 20, 2008

Now I have new problem with stackoverflow.

When I loaded Sednica object from database and want get PrisutnostList collection from property I use lazyload. PrisutnostList collection populated with Prisutnost child objects.

The stackoverflow appear in Prisutnost in Child_Fetch(CriteriaReaderSednica criteria) when call LoadProperty<Sednica>(SednicaProperty, criteria.Sednica) method. I suppose that problem is child-parent relation in Sednica and Prisutnost class. In Sednica class, PrisutnostList is not child and in Prisutnost class, Sednica is not child. What I need to do to avoid this problem? The snippet of code is below:

public class Sednica : BaseObject<Sednica>

{

    private static PropertyInfo<PrisutnostList> PrisutnostListProperty = RegisterProperty<PrisutnostList> (typeof (Sednica), new PropertyInfo<PrisutnostList>("PrisutnostList", "PrisutnostList"));

public PrisutnostList PrisutnostList

{

    get

    {

         if (!(FieldManager.FieldExists(PrisutnostListProperty)))

    {

         LoadProperty<PrisutnostList>(PrisutnostListProperty, PrisutnostList.GetPrisutnostBy(this));

     }

     return GetProperty<PrisutnostList>(PrisutnostListProperty);

    }

    set

    {

         SetProperty<PrisutnostList>(PrisutnostListProperty, value);

     }

}

}

// Prisutnost class

public class Prisutnost : BaseObject<Prisutnost>

{

   

private static PropertyInfo<Sednica> SednicaProperty = RegisterProperty<Sednica>(typeof(Prisutnost), new PropertyInfo<Sednica>("Sednica", "Sednica"));

public Sednica Sednica

{

get

{

var sednica = GetProperty<Sednica>(SednicaProperty);

if (sednica != null && sednica.IsLightWeight)

{

LoadProperty<Sednica>(SednicaProperty, Sednica.GetSednicaBy(sednica.Uid));

}

return GetProperty<Sednica>(SednicaProperty);

}

set { SetProperty<Sednica>(SednicaProperty, value); }

}

private void Child_Fetch(CriteriaReaderSednica criteria)

{

try

{

LoadData(criteria.Reader);

if (criteria.Sednica != null && !criteria.Sednica.IsLightWeight)

{

LoadProperty<Sednica>(SednicaProperty, criteria.Sednica);

}

}

catch (Exception ex)

{

if (ExceptionPolicy.HandleException(ex, "DataAccess Policy"))

throw;

}

}

}

Copyright (c) Marimer LLC