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.
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
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 ??
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.
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 ??
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
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?
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?
"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."
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?
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
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.
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.
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
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.
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