Cascading root object delete to children.

Cascading root object delete to children.

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


rxelizondo posted on Wednesday, January 20, 2010

Hello,

Consider your standard business collection such as the “Root > Collection > Child” collection. If you were to call the Delete() method on the Root object, you will noticed that only the Root object is marked as deleted but not the children.

Perhaps I am wrong, but I feel that the default behavior for calling the Delete() method on the Root object should be also to call the Delete() method on the children so that they can also be marked as deleted.

What do you guys think?

And yes, I know we can override the Delete() method but I am focusing here on the default behavior of the framework.

Thanks.

Calin replied on Thursday, January 21, 2010

Hi,

Are you sure you are using MarkAsChild(...) ? If you use this correctly you should have the desired behaviour.

Regards,

JonnyBee replied on Thursday, January 21, 2010

Hi,

From my point of view this is a question of how to enforce referential integrity in your database.

You could do:
How can you be sure that all referential child data have been loaded in your object structure? You shouldn't expect that - but rather handle this in a safe manner in your data access code or DB.

Keep in mind also that Csla supports both immediate and deferred delete - where immediate delete would typically mean "delete an object with id = ? and all its child data".  So I would prefer to keep the functionality as is.

ajj3085 replied on Thursday, January 21, 2010

I think the current implementation makes sense.  You may not want to delete child objects automatically in some scenarios.  Also, this makes behavior consistent when using managed or unmanaged fields.  In the unmanaged fields scenario, there is no way for the call to propogate. 

rxelizondo replied on Thursday, January 21, 2010

JonnyBee:

How can you be sure that all referential child data have been loaded in your object structure? You shouldn't expect that


Actually, I think the exact opposite, In my head, you *should* expect that all referential child data has been loaded into your object structure.

Specially, if you are doing a deferred delete (what you do when you call the Delete method on the root object) then you *must* definitely better be sure that the data on your cached business object (including all referential child data) is identical to the data that resides on the database before you do a save. I mean, the whole CSLA IsDiry, IsNew etc is based on that idea right?

If a user is doing a deferred delete on my business object and the data on the database doesn't match the business object then they would immediately receive some kind of concurrency exception thrown by my business object. I don't think that is a good idea for the user to be deleting and object where she expects to be deleting 5 child object and in actuality be deleting 10 because some other user updated the object behind her back. I think your business object should handle this type of concurrency issues by raising an exception.

But then there is Andy's point of view,


ajj3085:

I think the current implementation makes sense. You may not want to delete child objects automatically in some scenarios.


I agree with your view, but would't you agree that the majority of the root > child collections (including the project tracker examples) do not represent self lived children objects? most of the times the children represent simple associations with the parent. Basically the children have a Compositions relationship with the parent and not Aggregation.

for example, on the Project Tracker sample app, a Project contains a collection of ProjectResource objects and not of Resource objects. Also a Resource contains a collection of ResourceAssignment and not of Assignment object. This story goes on and on for 95% of the collections created (Root > Collection > Child).

Now, I agree that on an Editable Root List all children are basically self lived object but I am not talking about those on this post.

Has my respond cause any of you guys to change your minds on how you are looking at the root Delete method behavior or do you guys still feel strong about your position?

Thanks you.

tmg4340 replied on Friday, January 22, 2010

rxelizondo:
JonnyBee:
How can you be sure that all referential child data have been loaded in your object structure? You shouldn't expect that
Actually, I think the exact opposite, In my head, you *should* expect that all referential child data has been loaded into your object structure.

Not necessarily...

Say I have a customer grid, and in this grid, I'm displaying their primary address.  If my users can delete customers from this grid, then I may not have all of that customer's address records loaded as a business object in memory.  However, I sure do want to delete all the associated address records.  And even if I do have all their address records loaded into memory, I may not have the sum total of that customer's object graph (e.g. any orders they've made) loaded into memory.  But those need to be dealt with as well.

I look at it this way - databases don't automatically cascade deletes to referential child data.

I also agree with Andy in that a cascading delete isn't automatically possible with private backing fields, so the choice to not do it with the managed backing fields also probably took consistency into account.

HTH

- Scott

JonnyBee replied on Friday, January 22, 2010

Hi,

A recommended design strategy for CSLA Business Object is use-case driven. From my experience that often leads to BOs that contain a subset (aka section) of your domain model or even don't replicate the structure in the DB. If you have a "1 to 1" or "1 to n" relationship the use case often gives you one object for databinding to UI.

The point is -  a BO will often NOT have a 1:1 relationship to a DB Entity. 

Take f.ex an ordering system or crm system where a "delete" may actually turn out to be "change status to deleted" and do not delete the child/grandchild/... objects. 

ajj3085 replied on Friday, January 22, 2010

rxelizondo:
Actually, I think the exact opposite, In my head, you *should* expect that all referential child data has been loaded into your object structure.

No, that's not really reasonable, to the point where Csla supports lazy loading of child object properties.  I think its important that Csla is flexible enough to support both scenarios.

rxelizondo:
I agree with your view, but would't you agree that the majority of the root > child collections (including the project tracker examples) do not represent self lived children objects?

I'm sure there are enough in the minority that the flexibility to handle either scenario shouldn't be removed. If you want to argue majority, I would think the majority of people have the root BO take care of any reference cleanup in the database, so the state of the chlid objects is irrelevent.

And the change would still cause inconsistent behavior depending on if you're using managed vs. unmanaged fields.

rxelizondo replied on Friday, January 22, 2010

Thanks guys,

mmmmm…. well, I definitely see some good points being made here.

Quite honestly, I am still not sure how I feel about loading partial data into my business object. Up till now, I have never been into a situation where I must have a partially loaded business object to preserve recourses, to enhance performance or for some other reason.

But since the CSLA supports lazy loading, then my whole deal about the root object marking the child objects as deleted so that each child can delete itself is flawed. And the reason for this of course is because if the lazy loaded objects are never loaded then they will never me marked as deleted and therefore never get deleted.

I don’t know guys, I am having a hard time accepting the current behavior as our best option.

I still feel that deferred deletion (in the case of collections) should delegate the deletion implementation to *each individual* object and not have the root object do all the work. If the root object is going to do all the work then I think immediate deletion should be the option to use for that.

What do you guys think about having deferred deletion assume that all of the data that needs to be deleted should be loaded into the object and let each child delete itself?

Bear with me on this one, let’s think about this. If we are not able to separate the “deferred” and “immediate” deletion concepts like this, then what exactly is the philosophical difference between these two ways of deleting an object? I mean, they basically do the same thing in the same way so why not just get rid of deferred deletion?

Bottom line, deferred deletion lets each object handle its own deletion while immediate deletion handles everything totally separated.

Note: The above assumes basically not taking into account Andy’s point of view where sometime you may only want to have the root object deleted and not the children.

Lets keep an open mind here, I know that sometime I can get annoying discussing some of this thing, I am just looking for direction.


tmg4340:
I also agree with Andy in that a cascading delete isn't automatically possible with private backing fields, so the choice to not do it with the managed backing fields also probably took consistency into account.


Correct me if I am wrong but we already have other inconsistensies between managed and private backing fields so I am not quite sure if I personally feel that this would be a compelling reason to go one way or the other. But you know me, if I ruled the CSLA world private backing field would probably not exist by now so lets not go there :)

Thanks
Rene

RockfordLhotka replied on Friday, January 22, 2010

There are a few issues here.

First, if marking the root deleted cascaded down, the user would see an interesting UI artifact, which is that all their grids and listboxes would empty themselves. Marking a child in a list as deleted removes the item from the active list.

You can imagine that this would be confusing for many users, and in apps with lots of items in grids/lists it could cause poor performance (the user gets annoyed as it takes a couple seconds to drain the data from the datagrid controls...)

Second, immediate deletion was, imo, a mistake. It is really a specialized implementation of a Command object, and today it would be better implemented as a Command object. In 2001 there was no Command object though, and so immediate deletion was implemented as you see it.

Third, Andy is right - most people have the root object (or the database) delete all related child data. Remember that the object graph often doesn't directly match the (full) relational structure of the database. There is often "child data" that must be deleted that isn't part of the object graph at all. And I'm sure there are cases where child objects aren't deleted just because the root is deleted - in cases where the child object reflects a relationship rather than a direct data interaction.

For most users then, changing the existing behaviour would make them go through extra work - as they'd need to implement empty Child_Delete() methods that would get invoked for no reason since their root already did the work. So they'd have silly code in their app, and decreased performance at the UI and DAL levels.

Fourth, if you want to argue that CSLA doesn't natively support the minority model where child objects are marked for deletion and their Child_Delete() methods invoked, that's true. I don't recall this behavior being requested over the past decade, so it clearly hasn't been a high priority issue for anyone. But if there's a groundswell of support for the concept it is something I'd entertain for some future version.

Finally, the delete model today is actually fairly comparable to the fetch model. Usually people have their root object fetch all the data for the object graph (or much of it) using EF, L2S, a DataSet or a multi-select datareader. So each child doesn't fetch its own data, as that makes for incredibly terrible performance. Delete simply follows this same basic structure, for the same basic reasons.

rxelizondo replied on Friday, January 22, 2010

RockfordLhotka:

Fourth, if you want to argue that CSLA doesn't natively support the minority model where child objects are marked for deletion and their Child_Delete() methods invoked, that's true. I don't recall this behavior being requested over the past decade, so it clearly hasn't been a high priority issue for anyone. But if there's a groundswell of support for the concept it is something I'd entertain for some future version.


Right now, it looks to me that most people probably implement both immediate and deferred deletion the same way (basically deferred deletion data portal calls immediate deletion data portal behind the scenes).

Again, to me, immediate deletion would be better off implemented as code where everything occurs on one function in one shot, where as deferred deletion calls for putting on the surgical gloves and let each object do its own thing (just like insert/update works aka: calling Child_Delete() ).

This is how you would get the best of both worlds in my opinion. Do you want to nuke it all fast and furiously? Call immediate delete. Do you want more encapsulation? No problem, use deferred deletion. Everyone wins.

Think about it, *anyone* can create a command object that wipes everything off from the database, big deal. But to have independent control on each of the objects while they are being deleted like you do with update/insert? Well, that takes support form the CSLA that unfortunately does not exist (at least natively).


RockfordLhotka:

Finally, the delete model today is actually fairly comparable to the fetch model. Usually people have their root object fetch all the data for the object graph (or much of it) using EF, L2S, a DataSet or a multi-select datareader. So each child doesn't fetch its own data, as that makes for incredibly terrible performance. Delete simply follows this same basic structure, for the same basic reasons.


Here is the thing, first of all, fetching always leaves the status of the object with the correct value: IsDirty = False, IsDeleted = False, IsNew = False etc.

Compare that to calling the Delete method on the root object where the child objects are supposed to be deleted, for a moment in time, the root object will have a value of IsDeleted = True, but all the children will have a value of IsDelete = False. How does this makes any sense?

And when it comes to performance, I bet money that when the entity framework hits prime time, it will have all kind of performance enhancement features. So even if you put the code to have each business object deleted itself independently (Child_Delete() ), the entity framework is probably going to optimize it for you. I may be wrong but if that is the case then the whole performance argument will be meaningless then.


Anyway, just my 2 cents.

We can drop this thread since most likely wont get anywhere, I just had to have the last word :)

Thanks for putting up with me!

Copyright (c) Marimer LLC