Delete leaves Root and Children in Inconsistent State

Delete leaves Root and Children in Inconsistent State

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


The_Tosh posted on Wednesday, January 21, 2009

I am using managed properties, and CSLA 3.5.  For purposes of brevity in the post, I'll just show a couple of properties of sample objects in standard object syntax, rather than showing the declaration of the PropertyInfo fields.  Rest assured, though, that the PropertyInfo fields are in full use.

Class Person : BusinessBase
{
   int ID { get; set; }
   string Name { get; set; }
   PhoneNumber HomePhone { get; set; }
}

Class PhoneNumber : BusinessBase
{
   int ID { get; set; }
   string AreaCode { get; set; }
   string Number {get; set; }
}

 

Here's what happens when I try to delete a fully instantiated Person object. 

Starting with:  
  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = False
  Person.IsNew = False
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = False
  Person.HomePhone.IsNew = False

Make these calls:
  Person.Delete( );
  Person.Save( );

With code in Person.DataPortal_Delete to call a stored procedure to delete the full object graph (in this example, Person 2 and PhoneNumber 37), this is the end result.

  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = False
  Person.HomePhone.IsNew = False

How come the child PhoneNumber object isn't marked IsDirty = True, IsNew = True ??

I don't have any "Child_DeleteSelf" or "DataPortal_Delete" methods in the PhoneNumber class, because the "DataPortal_Delete" of the root object (Person) takes care of deleting all the data from the database. 

JoeFallon1 replied on Wednesday, January 21, 2009

You can pretty much guarantee that any code that looks like this is a bug:

 Person.Save( );

Your code should always update the reference.

e.g.

myPerson = myPerson.Save();

Joe

 

The_Tosh replied on Wednesday, January 21, 2009

Fortunantly, my code is better than what was posted.  It indeed is written like this:

myPerson.Delete( );
myPerson = myPerson.Save( );

So the most obvious error is eliminated, and the question remains, why isn't the child object marked IsDirty = True, IsNew = True ??

RockfordLhotka replied on Wednesday, January 21, 2009

Also, even if your child objects don't DO anything to delete themselves, you need to cascade the update call to them.

In your root object's DataPortal_Insert/Update/Delete methods you must at least call ChildUpdate() or otherwise tell the children to update themselves - otherwise how would they know to change their status to new/old/etc?

You can have empty Child_Delete() methods, which is probably appropriate here.

The_Tosh replied on Wednesday, January 21, 2009

Added a call to DataPortal.UpdateChild(this.HomePhone, this) in the Person DataPortal_Delete method.  That called in to the ChildDataPortal.Update for the child object, where it quickly hit this code and exited before it could do anything:

      if (busObj != null && busObj.IsDirty == false)
      {
        // if the object isn't dirty, then just exit
        return;
      }

The Child object isn't marked as IsDirty = True, nor is it marked as IsDeleted = True.  If it were, it looks like the ChildDataPortal would call the Child_DeleteSelf method, and life would be better.

Since the child IsDirty = False, it doesn't even get the chance to run Child_Update. 

Am I missing fundamental step in processing my scenario?  I have a fully populated object graph, with parent and child object, call parent.Delete( ), then parent = parent.Save( ).  What else do I need to do to get the child object to come out of all this with IsNew = True, IsDirty = True ??

RockfordLhotka replied on Wednesday, January 21, 2009

Maybe I missed something in your scenario.

 

Are you deleting all the children? In that case I typically just create a new (empty) list for the child list and use LoadProperty() to replace the previous (now useless) one.

 

Rocky

The_Tosh replied on Wednesday, January 21, 2009

I am deleting the child object from the parent's DataPortal Delete via a stored procedure.   I had gone down the path of replacing the parent's reference to the child with a new child and using LoadProperty.

That causes another inconsistency in the state of the objects, though.

From the initial post - here's the data in the object graph:

  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = False
  Person.IsNew = False
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = False
  Person.HomePhone.IsNew = False

After calling myPerson.Delete( ), myPerson = myPerson.Save ( ), and loading a new PhoneNumber instance in the Person DataPortal_Delete, the state of the object graph is this:  (values that change as a result of the Save( ) are shown in red )

  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 0
  Person.HomePhone.AreaCode = ''
  Person.HomePhone.Number = ''
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

So now after a Delete, the parent object (myPerson) retains values in its properties (Name, in this example), but the child object has its values cleared out.  The parent and the child don't behave the same.  I'm trying to achieve consistency in behavior of parent and child.  Either of the following outcomes would be fine:

1) Retain values in properties, but child flags change as parent flags do -
  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

or

2) Parent object gets "New"ed up as well as child object(s)
  Person.ID = 0
  Person.Name = ''
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 0
  Person.HomePhone.AreaCode = ''
  Person.HomePhone.Number = ''
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

Does this make sense?

RockfordLhotka replied on Wednesday, January 21, 2009

If you’ve deleted all children of the root in the database, then there are no children. So the child list should be empty. That’s the closest object match to the database reality, and that’s the only supported scenario for CSLA.

 

If you want to somehow leave deleted child object placeholders, then you’ll need to mark the child objects as deleted before calling Save(), or at least before calling UpdateChild().

 

Maybe (and I haven’t tried this), you could add this code in your root DP_D() method:

 

// delete all the data from the db

this.ChildList.Clear();  // “delete” all child objects

DataPortal.UpdateChild(ReadProperty(ChildListProperty)); // commit the deletes

 

I think you’ll still end up with an empty child collection though, because the default behavior of a BLB is to remove any deleted child objects from the collection.

 

So to overcome that, you’ll have to override the Child_Update() method of your BLB and implement your own algorithm to do what you want with the items in DeletedList.

 

Rocky

 

 

From: The_Tosh [mailto:cslanet@lhotka.net]
Sent: Wednesday, January 21, 2009 5:03 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Delete leaves Root and Children in Inconsistent State

 

I am deleting the child object from the parent's DataPortal Delete via a stored procedure.   I had gone down the path of replacing the parent's reference to the child with a new child and using LoadProperty.

That causes another inconsistency in the state of the objects, though.

From the initial post - here's the data in the object graph:

  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = False
  Person.HomePhone.IsNew = False

After calling myPerson.Delete( ), myPerson = myPerson.Save ( ), and loading a new PhoneNumber instance in the Person DataPortal_Delete, the state of the object graph is this:

  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 0
  Person.HomePhone.AreaCode = ''
  Person.HomePhone.Number = ''
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

So now after a Delete, the parent object (myPerson) retains values in its properties (Name, in this example), but the child object has its values cleared out.  The parent and the child don't behave the same.  I'm trying to achieve consistency in behavior of parent and child.  Either of the following outcomes would be fine:

1) Retain values in properties, but child flags change as parent flags do -
  Person.ID = 2
  Person.Name = 'Fred'
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 37
  Person.HomePhone.AreaCode = '555'
  Person.HomePhone.Number = '555-1212'
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

or

2) Parent object gets "New"ed up as well as child object(s)
  Person.ID = 0
  Person.Name = ''
  Person.IsDirty = TRUE
  Person.IsNew = TRUE
  Person.HomePhone.ID = 0
  Person.HomePhone.AreaCode = ''
  Person.HomePhone.Number = ''
  Person.HomePhone.IsDirty = TRUE
  Person.HomePhone.IsNew = TRUE

Does this make sense?



The_Tosh replied on Thursday, January 22, 2009

"If you’ve deleted all children of the root in the database, then there are no children. So the child list should be empty. That’s the closest object match to the database reality, and that’s the only supported scenario for CSLA."

The root has been deleted from the database as well, so by extension, the root object should be empty as well, as the closest match to the database reality.  The root, however, remains as a populated object placeholder.  The practice of replacing child object and child collections during delete processing with new, empty instances while leaving the root object as a populated placeholder is an inconsistency of object behavior that I'm trying to avoid. 

 

Note that in the sample code the root object does not contain a child collection, but simply a child object (derived from BusinessBase).  As such, ChildList.Clear( ) won't work, since it is not a child list.  One can't call .Delete( ) on the child object, because it is a child object, and that would cause the framework to throw a ChildDeleteException.  Neither can one call .DeleteChild( ), because that is an internal method that is not visible from within the root object. 

 

How does one mark a child object (which is not a member of a collection or business list base) for deletion?

JoeFallon1 replied on Thursday, January 22, 2009

In the Delete for the root object, after the DB has removed all the records, you just create a New child object and update your reference.

myChild=Child.NewChild(Me.ID)

Joe

 

The_Tosh replied on Thursday, January 22, 2009

Joe, you missed the point of the thread.  Updating the reference with a New child object gives you an new, empty child object while your root object retains all the values it had before it was deleted.

Reread post 30162, in this thread for an example of what this does, and for the outcome I'm trying to achieve.

RockfordLhotka replied on Thursday, January 22, 2009

I think this does what you want?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var obj = TheRoot.GetRoot();
      Console.WriteLine(obj.Child.IsNew);
      obj.Delete();
      obj = obj.Save();
      Console.WriteLine(obj.Child == null);
      Console.WriteLine(obj.Child.IsNew);

      Console.ReadLine();
    }
  }

  [Serializable]
  public class TheRoot : BusinessBase<TheRoot>
  {
    private static PropertyInfo<TheChild> ChildProperty = RegisterProperty(new PropertyInfo<TheChild>("Child", "Child"));
    public TheChild Child
    {
      get { return GetProperty(ChildProperty); }
      set { SetProperty(ChildProperty, value); }
    }

    public static TheRoot GetRoot()
    {
      return DataPortal.Fetch<TheRoot>();
    }

    #region Data Access

    private void DataPortal_Fetch()
    {
      Child = TheChild.GetChild();
    }

    protected override void DataPortal_Insert()
    {
      
    }

    protected override void DataPortal_Update()
    {

    }

    protected override void DataPortal_DeleteSelf()
    {
      DataPortal_Delete();
    }

    private void DataPortal_Delete()
    {
      Child = TheChild.NewChild();
    }

    #endregion

  }

  [Serializable]
  public class TheChild : BusinessBase<TheChild>
  {
    internal static TheChild NewChild()
    {
      return DataPortal.CreateChild<TheChild>();
    }

    internal static TheChild GetChild()
    {
      return DataPortal.FetchChild<TheChild>();
    }

    private void Child_Fetch()
    { }
  }
}

RockfordLhotka replied on Friday, January 23, 2009

In that case, in the root object do this:

    private void DataPortal_Delete()
    {
      Child.MarkAsDeleted();
    }

and in the child object do this:

    internal void MarkAsDeleted()
    {
      MarkNew();
    }

I believe that'll generate the result you are after.

The_Tosh replied on Friday, January 23, 2009

Yes, that does it.  Thank you.

To make it perfectly clear to any who are casually following along, the MarkAsDeleted method on the child object is a new method that I need to add to my Business Object.  MarkAsDeleted( ) is not part of the CSLA Framework.  Should my child object have children of its own, I would also need to implement MarkAsDeleted( ) on them, and call that right after calling this.MarkNew( ).

  internal void MarkAsDeleted()
  {
    MarkNew( );
    child.MarkAsDeleted( );
    anotherChild.MarkAsDeleted( );
  }

This is a by-product of the root object deleting all the data for the entire object graph.

Michael replied on Wednesday, March 04, 2009

We are developing an AutoCAD application using CSLA. In AutoCAD it is common for users to delete an object from a drawing, which causes the business object to delete itself from the database, and then undo the delete, which brings the object back into the drawing and should reinsert everything back into the db. I also expected the items in a child collection to be marked new and dirty after the parent is deleted.

Rocky's hack does the trick, but I agree with The_Tosh that "replacing child object and child collections during delete processing with new, empty instances while leaving the root object as a populated placeholder is inconsistent". By default, I believe you should be able to do this repeatedly:

obj.Delete();     // Mark for deletion
obj = obj.Save(); // Bye bye
obj = obj.Save(); // Oops, put it back, with all children as they were

ajj3085 replied on Thursday, March 05, 2009

I'm not sure that it should work this way by default; most people don't have this scenario, so I'm not sure having csla go through and fix everything up would be worthwhile for an object that is going to be on the garbage heap soon anyway.

Michael replied on Thursday, March 05, 2009

There's nothing to "fix up" - the data remains as it was, it just needs to be marked as new. The performance hit would be virtually nil, and nothing compared to calling a stored procedure over the network.

JoeFallon1 replied on Friday, January 23, 2009

The_Tosh:

Joe, you missed the point of the thread.  Updating the reference with a New child object gives you an new, empty child object while your root object retains all the values it had before it was deleted.

Reread post 30162, in this thread for an example of what this does, and for the outcome I'm trying to achieve.

Well I guess Rocky missed it too since he just recommended what I did.

It seems like you are doing something that doesn't make sense to either me or Rocky. An I am not sure why you think you need to do it. Maybe you should step back and re-think your position.

Joe

The_Tosh replied on Friday, January 23, 2009

Yes, you and Rocky are consistent in your approach.  I follow exactly what you're doing, and why. 

 

I'm developing the Business Layer, but not the UI that will consume it.  I'm trying to guarantee that my business objects don't give a UI developer unexpected / inconsistent results should he attempt to reuse the root object after Delete( ) and Save( ) by issuing another Save( ) in an effort to implement an UndoDelete feature.  With the standard implementation, this an UndoDelete implemented in this manner would succeed for the root object, but would fail for all the children.  I'd rather see it either fail for the root as well, or succeed for the children as well as the root.

 

This is an edge case with a small chance of ever actually happening, but I don't like releasing code that can mislead the UI developer.

RockfordLhotka replied on Thursday, January 22, 2009

It is the immediate parent object’s job to manage its relationship to the child.

 

BLB does this for you automatically (for the most part), so you don’t need to worry about it.

 

In CSLA 3.6 the field manager helps with some aspects of managing a child object relationship, but it doesn’t do everything. It is up to the immediate parent object (your code) to manage the aspects of the relationship not handled by field manager – including deleting the child (presumably resetting the value to null or something).

 

Rocky

 

 

From: The_Tosh [mailto:cslanet@lhotka.net]
Sent: Thursday, January 22, 2009 9:45 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Delete leaves Root and Children in Inconsistent State

 

"If you’ve deleted all children of the root in the database, then there are no children. So the child list should be empty. That’s the closest object match to the database reality, and that’s the only supported scenario for CSLA."

The root has been deleted from the database as well, so by extension, the root object should be empty as well, as the closest match to the database reality.  The root, however, remains as a populated object placeholder.  The practice of replacing child object and child collections during delete processing with new, empty instances while leaving the root object as a populated placeholder is an inconsistency of object behavior that I'm trying to avoid. 

Note that in the sample code the root object does not contain a child collection, but simply a child object (derived from BusinessBase).  As such, ChildList.Clear( ) won't work, since it is not a child list.  One can't call .Delete( ) on the child object, because it is a child object, and that would cause the framework to throw a ChildDeleteException.  Neither can one call .DeleteChild( ), because that is an internal method that is not visible from within my root object. 

How does one mark a child object (which is not a member of a collection or business list base) for deletion?



Copyright (c) Marimer LLC