Delete Rules

Delete Rules

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


Helen W posted on Thursday, July 06, 2006

Is there a recommended method of specifying business rules for validating whether an object can be deleted?

thx

Helen

msk replied on Thursday, July 06, 2006

Helen, You may want to take a look at the Project class in the ProjectTracker sample application.  This has a good example.  Firstly it implements static functions CanAddObject, CanDeleteObject, ... These allow the UI to check whether it has permissions without creating the object or attempting the operation.  The Project class also uses these functions internally in it's static factory methods and it overrides the Save function from the base class.  In the Project class there is a static delete function and you may also call .Delete - a subsequent call to Save will then start the deletion process. 

Martin.

skagen00 replied on Thursday, July 06, 2006

I think she might be talking about more "instance related rules" for deleting objects.

I think if you want to allow for databinding, you might want to have an instance level CanDelete() which might be a composite of the static CanDeleteObject and some business rules relating to the instance. Make a delete UI available when CanDelete is true, and then check the CanDelete method when attempting to Delete() an object (assuming you're talking about an editableroot).

I'd be interested to hear what others might be doing.

 

Helen W replied on Thursday, July 06, 2006

Yes, I am talking about instance-level deletion. I do not want to use the standard Authorization methods with BO rules. A typical example could be that you do not want to delete a look-up record if it is in use by another object (i.e. foreign key relationship).

I too would be interested in how others are handling this.

skagen00 replied on Thursday, July 06, 2006

In some ways this parallels the idea that one can Save an object if IsValid. (well, if it's dirty too but that's more for performance/common sense reasons).

Essentially, IsDeletable() with a corresponding set of business rules. For me, I wouldn't want to delete an individual if they have transaction history. I wouldn't want to delete a transactional record if it has been posted. Absolutely, there is less of a substance behind these instance level needs for the ability to delete a record, but it sure is similar in some respects. A ValidationRules instance is flexible for storing knowledge of this behavior but it's not really built into the framework to track the "brokenness" of rules (not checked when PropertyHasChanged(), etc).

 

jemmer replied on Thursday, July 06, 2006

It seems to me that any "before-the-fact" mechanisms that would inform the user of potential delete conflicts or prohibitions (FK, etc) could only be used at best as a UI convenience.  This is because of potential multi-user considerations. That is, the only code capable of knowing for certain whether a foreign key relationship exists or doesn't exist is the same code that would perform the delete within the context of the same transaction that the test for "delete-ability" is done.  Any code executed outside the context of that transaction cannot be guaranteed that somebody else won't come along and rock the boat by adding or removing relationships or data in between the time you informed the user and the time they actually hit the Save button.

Thus, if you try to inform the user that they can or cannot do the delete, you cannot know for sure that they can do it until they actually try.  It may be helpful to inform the user before the fact that the attempt may or may not succeed, but it is at best an approximation of what will really happen.

We've dealt with this (in 1.1) by requiring that all delete attempts will be done within the context of a transaction.  The stored procedure that is executed first tests all potential conflicts and if it finds none, then executes the delete.  A unique return value is assigned for each potential conflict situation.  If a conflict is detected,a return code describing it is set, and the stored procedure exits.  The DataPortal code tests the return value after executing the stored procedure, and if it is one of the values that indicates a conflict, rolls back the transaction and throws an custom exception which includes the return value.  The UI code can then catch the execption and test this return value and give specific warnings, though generally we've found it's just as useful to simply throw up a messagebox with the somewhat vague message declaring that the object is in use and cannot be deleted.

Most of the time that is sufficient, since everybody knows you probably can't delete a customer whose been around any amount of time, since they are very likely to have activity.  Deletes are done because somebody made a mistake setting up a new customer and they'd like to start over.  These are the most likely cases where a delete is likely to succeed.  (Purging of historical data to allow for subsequent deletes is another story).

We also never never use cascade deletes since most of us who tried it have a toe or two missing from the gunshot aimed at the foot because something got deleted when it really shouldn't have... Big Smile [:D]

odie replied on Thursday, July 06, 2006

It seems to me (correct me if I'm wrong) that you would have to query the database first to see if the particular item is in use anywhere. If this is the case why not create a CommandBase object like "CanDeleteCommand" (similar to ExistsCommand in project tracker sample) then when the user selects delete run this commend and respond accordingly.

RockfordLhotka replied on Friday, July 07, 2006

odie:
It seems to me (correct me if I'm wrong) that you would have to query the database first to see if the particular item is in use anywhere. If this is the case why not create a CommandBase object like "CanDeleteCommand" (similar to ExistsCommand in project tracker sample) then when the user selects delete run this commend and respond accordingly.

Just remember that this is never safe. You can do this as a helper to make the user experience better, but you can never count on such a result. The reason is that there could be timing issues with multiple users. Remember that the data in your objects is ALWAYS an outdated copy of the real data in the database!

  1. User A runs the Command to see if the data exists - it does not, so user A's objects all think life is good
  2. User B adds the key bit of data - so now user A's objects erroneously think life is good, when in fact it is bad
  3. User A commits their data, believing it is OK - when in fact it is not, because of user B's action

The only way to enforce data-level rules is at the database, within the context of a transaction. Nothing we do with objects can change this, because objects (or datasets or anything like that) always contain a COPY of the data, not the real data.

This is not to say that using such a Command object is bad. On the contrary, it is often good, because the NORMAL case is that the Command object would catch the problem and the user would have a better experience overall. But the point is that you can't COUNT on the Command object's results. In the final analysis you still need to deal with the possible conflict during the data operation itself.

odie replied on Friday, July 07, 2006

I didn't elaborate but the use of the CommadBase was a quick check to see if you should even try to delete. If the command returns false the you know you can't perform the delete.

I agree fully that you still have to perform the check when you actually try to delete the data in case anything changed as you indicated Rocky.

Copyright (c) Marimer LLC