One of the points of the FileManager classes is they give you the same basic features as standard file access. But then can be extended to handle additional features and functions. I realized I have not written an article covering the basic file access functions and how to extend them so it seems like a good time, and since I had a client asking about record deleting this morning it seems like a good place to start
The Basics
I have said it many times, but it bears repeating, there is no magic in the FileManage Classes, no smoke, no mirrors, no hidden black-box code. If you dig into the code you will find that it is doing standard HAdd, HModify, HDelete, and HReadSeekFirst statements. At the end of the day, the FM is simply a very fancy wrapper that manages a structured array and and provides it is a class format that can be extended as needed
So how do you do a delete?
mgrWorkOrderHdr.SaveRecord(recWorkOrderHdr,recWorkOrderHdr.WorkOrderHdrId,wxManagerClass.eDelete,errMessage)
We see here an instance of a Manager class for WorkOrderHdr and we are calling SaveRecord.
The first parameter is an instance of the record class for this manager.
The second parameter is the primary key for the specific record
The third parameter is the action. eAdd, eChange or eDelete, in this case, eDelete is what we want
The fourth parameter is optional and you often see us write the code without it, although we are passing it in as a parameter it is actually used to retrieve any error returned from the method. The function returns a True/False indicating success, and the fourth parameter has the details of any error if the result was false. We will explore this more in a moment.
So for a simple delete that is all there is to it
Child Management
Be sure to read the separate blog post covering the Child Management features of the FM. As it covers all the details. But for a quick summary, there is a virtual method in the FM called RIDelete, this method is called from the SaveRecord code when the action is eDelete. This provides the ability for you to easily extend your manager class with any extra logic required for a delete.
In the Child Management Article you will find that the standard code we use looks like this
PROCEDURE RIDelete(argActiontype, argmessage = "")
Retval is boolean = False
// Add any code here that you want to check and stop the delete if that is an RI Constraint.
//IF we make it to here we are good to delete
// Handle Deleting Children
Rec.LoadChildren() // Make sure Children are loaded first
IF NOT Rec.mgrWorkOrderDtl.ChildDeleteAll(argmessage) THEN
RESULT False
END
RESULT Ancestor.RiDelete(argActiontype,argmessage)
This will automagically delete the children when the parent is deleted. and it works with other child management features built into the FM. Notice the argMessage parameter being used and passed in this code, which lets any errors from the child manager bubble up to the parent manager and eventually will get back to our SaveRecord call in the fourth (errMessage) parametered we passed.
Extending the Delete Logic further
We often have referential integrity (RI) code we need to apply during deletes. For example, if there has been a payment made against the WorkOrder we don’t want to allow it to be deleted. The RIDelete Method is where we apply this code as well. And since we have the WorkOrderHdrId in the Payment table and it is indexed, we can use a simple HReadSeekFirst statement to do that.
So now our RIDelete Method looks like this:
PROCEDURE RIDelete(argActiontype, argmessage = "")
Retval is boolean = False
// Add any code here that you want to check and stop the delete if that is an RI Constraint.
IF HReadSeekFirst(Payment,WorkOrderHdrID,rec.WorkOrderHdrID) THEN
argmessage = "Cannot delete, there has been a payment"
RESULT False
END
//IF we make it to here we are good to delete
// Handle Deleting Children
Rec.LoadChildren() // Make sure Children are loaded first
IF NOT Rec.mgrWorkOrderDtl.ChildDeleteAll(argmessage) THEN
RESULT False
END
RESULT Ancestor.RiDelete(argActiontype,argmessage)
So now if it finds a payment linked to the work order RIDelete returns a false and argMessage gets set to an error indicating why. And as discussed above this bubbles up to our SaveRecord call, so we write that code like so:v
errMessage is string
IF NOT mgrWorkOrderHdr.SaveRecord(recWorkOrderHdr,recWorkOrderHdr.WorkOrderHdrId,wxManagerClass.eDelete,errMessage)
Info(errMessage)
END
How many levels can we go?
If you were paying attention during the Child Management discussion, you may have notice that it is using a separate manager class for the Child Table, which means that it also has all of this RIDelete logic as well and everything can bubble up. I have done RIDelete logic that went from Parent to Child to Grand Child.
Let’s extend our example a bit further and see what that looks like at the child level. Perhaps we don’t want to delete our Work Order if there has been any Purchase Order for parts required for the Work Order. However the link for that is from PurchaseOrderDtl to WorkOrderDtl, how would we do that?
As discussed we would have a WorkOrderDtl manager and an instance (mgrWorkOrderDtl) of that inside the WorkOrderHdr manager (mgrWorkOrderHdr), again be sure to read Child Management article for more details. So in the WorkOrderDtl Manager we would have an RIDelete method like so:
PROCEDURE RIDelete(argActiontype, argmessage = "")
Retval is boolean = False
// Add any code here that you want to check and stop the delete if that is an RI Constraint.
IF HReadSeekFirst(PurchaseOrderDtl,WorkOrderDtlID,rec.WorkOrderDtlID) THEN
argmessage = "Cannot delete, there have been parts ordered"
RESULT false
END
//IF we make it to here we are good to delete
RESULT Ancestor.RiDelete(argActiontype,argmessage)
All of the code we have already created for the Parent remains as is. Now when the ChildDeleteAll fires in the Parent RIDelete method it will eventually fire the RIDelete of the Child Manager, and if it finds a Purchase order it will set argMessage and return a false, which in turn will bubble up through the ChildDeleteAll to the Parent’s RIDelete, and eventually to our SaveRecord call. Letting us know we can’t delete the Work Order.
Extend as you like
This has just been a few examples of how we use the RIDelete Method, but as is the point with the entire FileManager class we haven’t locked you into any specific actions or functions, we just give you a class wrapper written in such a way to give you a number of ways to extend the functionality however you need, while still staying inside the FM framework. In future articles I will show you similar methods we can use to extend the logic when Adding or Modifying records.
Pete: I think you left out a “RESULT = False” in the “Extending the Delete Logic further “IF HReadSeekFirst(…” example statement.
LikeLike
You are correct, I have edited the article. Thanks!
LikeLike