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.
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.
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
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
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
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.
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(); //}}
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
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();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.
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!
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 ;)
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