Release 10.1A: OpenEdge Development:
ProDataSets

> ## Building a business entity procedure to support the ProDataSet

> Next, you’ll write the procedure that really represents the `dsOrder` ProDataSet itself. This is a variation of the `OrderSupport.p` procedure you’ve 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. Figure 10–2 is a sketch of the larger entity object.

> Figure 10–2: 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’ll attach validation logic to it as well.

> There isn’t 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 won’t 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’ll 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’s 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 doesn’t have such a manager, we put `saveChanges` into the `Order` entity itself.

> This is the end of the code for `OrderEntity.p`.

Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095