Can not directly save a child object (CSLA 2.1.4)

Can not directly save a child object (CSLA 2.1.4)

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


bkirkman posted on Tuesday, June 30, 2009

I am getting the above error trying to Save() my object. I have a few different ways to get around this eror without success. The last was to make the child object 'switchable'. I am looking for suggestions on how to make this work.

Basically, I have an Estimate (BusinessBase - Editable) that contains Estimate Components(BusinessListBase ) that contains ComponentColors(BusinessListBase). I am trying to edit a specific component color and save it.

Code Snippets:
[Serializable()]
public class ComponentColors : BusinessListBase
{
...
protected override void DataPortal_Update()
{
RaiseListChangedEvents = false;
foreach (ComponentColor item in DeletedList)
item.DeleteSelf();
DeletedList.Clear();
foreach (ComponentColor item in this)
if (item.IsNew)
item.Insert(this);
else
item.Update(this);
RaiseListChangedEvents = true;
}
...
}

Switchable Business Base
public class ComponentColor : BusinessBase
{
...
#region Factory Methods

public static ComponentColor NewComponentColor()
{
return DataPortal.Create(new RootCriteria());
}

internal static ComponentColor NewComponentColorChild()
{
ComponentColor obj = new ComponentColor();
obj.MarkAsChild();
return obj;
}

public static ComponentColor GetComponentColorRoot(int id, int compid)
{
return DataPortal.Create(new RootCriteria(id, compid));
}

internal static ComponentColor GetComponentColorChild(SafeDataReader dr)
{
return new ComponentColor(dr);
}

private ComponentColor()
{
MarkAsChild();
}

private ComponentColor(SafeDataReader dr)
{
MarkAsChild();
Fetch(dr);
}
#endregion

#region Data Access
//Dual Criteria Classes to make this "switchable"
[Serializable()]
private class RootCriteria
{
private int _id;
private int _compid;

public int Id
{
get { return _id; }
}

public int CompId
{
get { return _compid; }
}

public RootCriteria(int id, int compid)
{
_id = id;
_compid = compid;
}

public RootCriteria()
{ }
}

private class ChildCriteria
{ }

[RunLocal()]
private void DataPortal_Create(RootCriteria criteria)
{
DoCreate(criteria);
}

[RunLocal()]
private void DataPortal_Create(ChildCriteria criteria)
{
MarkAsChild();
DoCreate(criteria);
}

private void DoCreate(object criteria)
{
// load default values here
if (criteria is RootCriteria)
{
RootCriteria crit = (criteria as RootCriteria);
_id = crit.Id;
_component_id = crit.CompId;
_colorlistid = 0;
_colorlistname = string.Empty;
_pct = 0;
_position = 0;
}
else
{
_id = 0;
_component_id = _id;
_colorlistid = 0;
_colorlistname = string.Empty;
_pct = 0;
_position = 0;
}
}

private void DataPortal_Fetch(RootCriteria criteria)
{
using (SqlConnection cn = new SqlConnection(Database.FGSConnection))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandType = CommandType.StoredProcedure;
cm.CommandText = "GetComponentColor";
cm.Parameters.AddWithValue("@Id", criteria.Id);
// TODO: create data reader to load values
using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))
{
DoFetch(dr);
}
}
}
}

private void Fetch(SafeDataReader dr)
{
MarkAsChild();
DoFetch(dr);
}

private void DoFetch(SafeDataReader dr)
{
// TODO: load values
_id = dr.GetInt32("id");
_component_id = dr.GetInt32("component_id");
_colorlistname = dr.GetString("name");
_colorlistid = dr.GetInt16("colorlist_id");
_pct = dr.GetInt16("pct");
_position = dr.GetInt16("position");

MarkOld();
}

internal void Insert(EstimateComponent parent)
{
// TODO: insert values
// if we're not dirty then don't update the database
if (!this.IsDirty) return;

using (SqlConnection cn = new SqlConnection(Database.FGSConnection))
{
cn.Open();
// Insert data
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandType = CommandType.StoredProcedure;
cm.CommandText = "InsertComponentColor";
SqlParameter param =
new SqlParameter("@Id", SqlDbType.Int);
param.Direction = ParameterDirection.Output;
cm.Parameters.Add(param);
cm.Parameters.AddWithValue("@ComponentId", _component_id);
cm.Parameters.AddWithValue("@ColorListId",_colorlistid);
cm.Parameters.AddWithValue("@Pct",_pct);
cm.Parameters.AddWithValue("@Position",_position);

cm.ExecuteNonQuery();

_id = (int)cm.Parameters["@Id"].Value;
}
MarkOld();
}

}

internal void Update(EstimateComponent parent)
{
// TODO: update values
// if we're not dirty then don't update the database
if (!this.IsDirty) return;
if (this.IsNew) return;

using (SqlConnection cn = new SqlConnection(Database.FGSConnection))
{
cn.Open();
// Insert data
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandType = CommandType.StoredProcedure;
cm.CommandText = "UpdateComponentColor";
cm.Parameters.AddWithValue("@Id", _id);
cm.Parameters.AddWithValue("@ComponentId", _component_id);
cm.Parameters.AddWithValue("@ColorListId", _colorlistid);
cm.Parameters.AddWithValue("@Pct", _pct);
cm.Parameters.AddWithValue("@Position", _position);

cm.ExecuteNonQuery();

}
MarkOld();
}

}
...
#endregion
}


Code in RowUpdating:

...
//Set Values in edited ComponentColor
ComponentColor color = ComponentColor.GetComponentColorRoot(editid, compid);
//Edit color component
color.Percent = Convert.ToInt16(txtPercent.Text);
color.Position = Convert.ToInt16(cmbPosition.SelectedItem.Value);
color.ColorListId = Convert.ToInt16(cmbColorList.SelectedItem.Value);
color.Save();
...


Inner Exception:
System.NotSupportedException: Can not directly save a child object at Csla.BusinessBase`1.Save() in C:\FGS\csla20cs-2.1.4-070223\csla20cs\csla20cs\Csla\BusinessBase.cs:line 148 at
Estimate_EditColorComp.grdColors_RowUpdating(Object sender, GridViewUpdateEventArgs e) in c:\FGS\Estimate\Estimate\EditColorComp.aspx.cs:line 195 at System.Web.UI.WebControls.GridView.OnRowUpdating(GridViewUpdateEventArgs e) at
System.Web.UI.WebControls.GridView.HandleUpdate(GridViewRow row, Int32 rowIndex, Boolean causesValidation) at System.Web.UI.WebControls.GridView.HandleEvent(EventArgs e, Boolean causesValidation, String validationGroup) at
System.Web.UI.WebControls.GridView.OnBubbleEvent(Object source, EventArgs e) at System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) at System.Web.UI.WebControls.GridViewRow.OnBubbleEvent(Object source, EventArgs e) at
System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) at System.Web.UI.WebControls.LinkButton.OnCommand(CommandEventArgs e) at System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument) at
System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) at
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

RockfordLhotka replied on Tuesday, June 30, 2009

The very definition of a child object is that it is persisted as part of its parent. That's a fancy way of saying that you can't save a child object, because that's how CSLA is designed.

Even if you defeat the normal protections around saving a child object (and you can), you'd confuse the parent object and that would cause other problems.

If you need to save this object directly, then it is a root object, not a child object. And if that's the case, then the object can't be contained by another object, it must stand alone.

JonnyBee replied on Wednesday, July 01, 2009

Hi,

Given your model of Estimate (BusinessBase - Editable) that contains Estimate Components(BusinessListBase ) that contains ComponentColors(BusinessListBase) - you should always call Save on Estimate.

Within the DataPortal_xyz you can check IsSelfDirty to determine if the given instance of any of the objects to make sure you save all the changes the user has made.

Another possible solution is to make ComponentColors a ReadOnlyList and when the user wants to edit/add a color you retrive/create a ComponentColorRoot object for edit.

/Jonny

bkirkman replied on Wednesday, July 01, 2009

I was not thinking in terms of the overall parent. I just thought I should be able to go to the immediate parent and save or make it switchable and save the child. Once I went to the overall parent(Estimate) and called the save there it worked just fine. Thanks to both you and Rocky for helping to take the blinders off.

Copyright (c) Marimer LLC