Try OpenEdge Now
skip to main content
ProDataSets
Advanced Update Operations : Building a business entity procedure to support the ProDataSet
 

Building a business entity procedure to support the ProDataSet

Next, you will write the procedure that really represents the dsOrder ProDataSet itself. This is a variation of the OrderSupport.p procedure you have written before. Call it OrderEntity.p.
The goal of this part of the exercise is to create a business entity object that manages dsOrder, expanding on the data access procedure you created earlier. The following figure is a sketch of the larger entity object.
Figure 17. Business entity procedure
The data access object becomes part of a larger Order entity or business object. The entity presents an API to the outside world that supports specific types of access to the object. Any requests that need to fill or save data use the business entity procedure as a gateway to the data managed in the data access object.
Validation logic for saving changes and other business logic associated with the ProDataSet are also encapsulated within the entity so that the logic is always executed consistently whenever the ProDataSet is referenced. Now you are working with a true business object.
Procedure OrderEntity.p is a simple example of the business entity procedure. It manages the API the client window uses to test the procedures and coordinate access to the data. Later, you will attach validation logic to it as well.
There is not very much code in the procedure, partly because some of it is in OrderSource.p and partly because some of it will be moved to a new general purpose dynamic procedure to handle the whole save-changes process.
This is the procedure that actually "owns" the ProDataSet instance on the server side. It is this procedure's ProDataSet instance that the FILL events and the Data-Sources are attached to, and this instance that is actually filled with data to be passed back to the client.
OrderEntity.p will be run PERSISTENT in support of the client window, as a server side procedure, either locally in the client process or remotely. It first gets the handle of the ProDataSet and uses this to set up a CLOSE trigger for the procedure to clean up, as shown:
/* OrderEntity.p -- Entity procedure for ProDataSet dsOrder */
{dsOrderTT.I}
{dsOrder.i}

DEFINE VARIABLE hDataSet AS HANDLE  NO-UNDO.
DEFINE VARIABLE hSourceProc AS HANDLE  NO-UNDO.
DEFINE VARIABLE lError      AS LOGICAL NO-UNDO.

hDataSet = DATASET dsOrder:HANDLE.

ON CLOSE OF THIS-PROCEDURE DO:
  DYNAMIC-FUNCTION("detachDataSet" IN hSourceProc, INPUT hDataSet).
  DELETE PROCEDURE hSourceProc.
  DELETE PROCEDURE THIS-PROCEDURE.
END.
Remember that you cannot obtain a handle such as hDataSet in the procedure main block and then use it inside internal procedures that have a ProDataSet as a parameter passed BY-REFERENCE. The handle will not be valid in those procedures because they are pointing to the external ProDataSet instance. This code gets the handle at the top of the main block simply because there is a limitation in the DYNAMIC-FUNCTION syntax that does not recognize the form DATASET dsOrder:HANDLE as a parameter. (This is actually the same restriction that already exists for buffer handle references.) Thus, you need to obtain the handle in advance so that you can simply name the handle variable in the function reference.
Next, the main block starts the data access procedure and runs the attachDataSet function for the local ProDataSet instance:
RUN OrderSource.p PERSISTENT SET hSourceProc.

lError = DYNAMIC-FUNCTION("attachDataSet" IN hSourceProc, hDataSet).
Because the methods in OrderSource.p operate on the ProDataSet instance passed into it, a single instance of the procedure itself could support any number of external instances of the ProDataSet. Therefore, in your application you could check whether there is already a running instance of a procedure such as this and use its handle if there is.
Remember that the fetchOrder procedure in OrderSource.p is intended to be called from its enclosing business entity procedure. Following is the version of fetchOrder that a procedure outside the Order entity calls to get an Order back. It defines its own OUTPUT parameter explicitly BY-VALUE to assure that no other procedure tries to pass in another ProDataSet instance BY-REFERENCE. This is because it is the instance in OrderEntity.p that has been attached to the FILL events and Data-Sources, and only that instance can be filled properly.
Procedure fetchOrder turns around and passes in the Order Number value to fetchOrder in OrderSource.p, along with its own ProDataSet instance BY-REFERENCE. In this way, it makes sure the local instance is used. For example:
PROCEDURE fetchOrder:
  DEFINE INPUT  PARAMETER piOrderNum AS INTEGER NO-UNDO.
  DEFINE OUTPUT PARAMETER DATASET    FOR dsOrder BY-VALUE.

  /* This turns around and runs an equivalent procedure in the Data-Source
     procedure, passing in the static dataSet. */
  DYNAMIC-FUNCTION("attachDataSet" IN hSourceProc, hDataSet).
  RUN fetchOrder IN hSourceProc (INPUT piOrderNum,
    INPUT-OUTPUT DATASET dsOrder BY-REFERENCE).
  DYNAMIC-FUNCTION("detachDataSet" IN hSourceProc, INPUT hDataSet).
END PROCEDURE. /* fetchOrder */
Finally, there is a saveChanges procedure in OrderEntity.p. This attaches the Data-Sources to make sure they are there for the save, and then runs a new procedure that you will write next, which captures all the standard save logic in a single dynamic procedure. It then detaches the Data-Sources from the ProDataSet, as shown:
PROCEDURE saveChanges:
  DEFINE INPUT-OUTPUT PARAMETER DATASET FOR dsOrder.

  DEFINE VARIABLE hDataSet AS HANDLE NO-UNDO.

  hDataSet = DATASET dsOrder:HANDLE.
  DYNAMIC-FUNCTION("attachDataSet" IN hSourceProc, INPUT hDataSet).
  RUN commitChanges.p (INPUT-OUTPUT DATASET dsOrder BY-REFERENCE).
  DYNAMIC-FUNCTION("detachDataSet" IN hSourceProc, INPUT hDataSet).
END PROCEDURE. /* saveChanges */
As you can see, saveChanges is actually entirely generic. It could operate on any ProDataSet, as long as it knows the procedure handle of the data access procedure that is stored in hSourceProc. If you had a generic session manager that coordinated running procedures on the server, your client could simply run saveChanges in that session manager, which could use the ProDataSet parameter to identify the ProDataSet type or name (dsOrder in this case), and set hSourceProc to the data access procedure for that ProDataSet. Since our simple example does not have such a manager, we put saveChanges into the Order entity itself.
This is the end of the code for OrderEntity.p.