Using Invokable Business Entity Methods with Dataset Model Classes

Invokable methods in Business Entities provide a very efficient way to implement reusable business logic in a Business Entity. Invokable methods are accessible both through the Service Interface (on the AppServer or from other business logic) or through the Service Adapter (from the GUI client).

Invokable methods need to provide a standardized signature:

The Business Entities dataset as an INPUT-OUTPUT parameter and a serializable object (Consultingwerk.ISerializable derived type). Note that the requirement that the parameter object (second parameter) implements the ISerializable interface is relaxed when the method only needs to be called from the AppServer.

Example of an invokable method
METHOD PUBLIC VOID PutCustomerOnHold (INPUT-OUTPUT DATASET dsCustomer, 
                                      poParameter AS Demo.PutOnHoldParameter):
    
    DEFINE VARIABLE oRequest AS FetchDataRequest NO-UNDO . 
    
    /* validation */
    ObjectAssert:IsValid (poParameter, "poParameter":U) .
    Assert:GreaterThanZero (poParameter:CustNum, "CustNum":U) .
    
    IF CharacterType:IsNullOrEmpty(poParameter:Comments) THEN 
        UNDO, THROW NEW Progress.Lang.AppError ("You must supply a comment to put a customer on hold.", 0) .
        
    /* fetch customer */
    oRequest = NEW FetchDataRequest ("eCustomer",
                                     SUBSTITUTE ("for each eCustomer where eCustomer.CustNum = &1",
                                                 poParameter:CustNum)) .
    THIS-OBJECT:FetchData (oRequest) . 
        
    FIND FIRST eCustomer . 
            
    /* Process data */
    THIS-OBJECT:TrackingChanges = TRUE . 
    
    ASSIGN eCustomer.Comments    = SUBSTITUTE ("Put on hold: &1~n&2", 
                                               STRING (NOW, "99.99.9999 hh:mm:ss":U),
                                               poParameter:Comments)  
           eCustomer.Terms       = "PREPAID ONLY":U
           eCustomer.Creditlimit = 0 .
            
    /* Save changes */                
    THIS-OBJECT:SaveChanges() .                
            
    DatasetHelper:ThrowDatasetErrors (DATASET dsCustomer:HANDLE) .                
            
END METHOD .

This example of an invokable method expects the PutCustomerOnHoldParameter class as a parameter. The dataset dsCustomer is not used for input. It's INPUT-OUTPUT definition is just to justify the requirement for an invokable method. In the first phase of the method, the input object and the properties of the object instance are validated. The second phase reads the customer with the given customer number. This is performed by calling into the Business Entities FetchData method. To process the data some fields of the temp-table record are updated and in the last phase those changes are saved using the SaveChanges method (leveraging all validation in the Business Entity or Data Access class).

Note that when returning from the method, the Business Entities dsCustomer DATASET is returned to the caller. So the caller will receive the updated eCustomer record.

Invoking the method from the Service Adapter

Sample of invoking the method through the Service Adapter (client)
{Demo/Customer/dsCustomer.i}

DEFINE VARIABLE oParameter AS PutOnHoldParameter NO-UNDO . 

oParameter = NEW PutOnHoldParameter (42, "We need to talk to this customer") .

FrameworkSettings:ServiceAdapter:InvokeMethod ("":U,                                     /* default partition */
                                               "Demo.Customer.CustomerBusinessEntity":U, /* Business Entity name */
                                               "PutCustomerOnHold":U,                    /* method name */
                                               INPUT-OUTPUT DATASET dsCustomer,
                                               oParameter) .

Invoking the method from the Service Interface

Sample of invoking the method through the Service Interface (business logic)
{Demo/Customer/dsCustomer.i}

DEFINE VARIABLE oParameter AS PutOnHoldParameter NO-UNDO . 

oParameter = NEW PutOnHoldParameter (42, "We need to talk to this customer") .

ServiceInterface:InvokeMethod ("Demo.Customer.CustomerBusinessEntity":U, /* Business Entity name */
                               "PutCustomerOnHold":U,                    /* method name */
                               INPUT-OUTPUT DATASET dsCustomer,
                               oParameter) .

Creating a proxy method in the DatasetModel class 

DatasetModel classes provide a highly efficient way of interacting with a Business Entity from custom code. By default they provide simple access to the read and update functionality of the business entity.

The Business Entity Designer (the ModelClassGeneratorPlugin) can create proxy methods for invokable methods in the business entity when "regenerating" the Business Entity source code from the Business Entity Designer. This functionality relies on an annotation outside of the invokable method:

Annotation sample
@InvokeMethod (template="invoke-receive-dataset") . 
/*------------------------------------------------------------------------------
    Purpose: Puts the Customer on hold 
    Notes:   Client callable method
    @param dsCustomer INPUT-OUTPUT DATASET
    @param poParameter The Parameter Object for this method 
------------------------------------------------------------------------------*/
METHOD PUBLIC VOID PutCustomerOnHold (INPUT-OUTPUT DATASET dsCustomer, 
                                      poParameter AS Demo.PutOnHoldParameter):

The @InvokeMethod annotation is required and the template parameter defines a template for the proxy method to be used when regenerating the dataset model class:

Sample of the PutCustomerOnHold method in the CustomerDatasetModel_Generated class
/*------------------------------------------------------------------------------
    Purpose: Invokes the Business Entity Method PutCustomerOnHold  
    Notes:   Receives the dataset PutCustomerOnHold from the backend
    @param poParameter The Parameter Object for this method 
------------------------------------------------------------------------------*/
METHOD PUBLIC VOID PutCustomerOnHold (poParameter AS Demo.PutOnHoldParameter):
        
    DATASET dsCustomer:EMPTY-DATASET () .
        
    IF THIS-OBJECT:UseInterface = Consultingwerk.OERA.UseInterfaceEnum:ServiceAdapter THEN 
        Consultingwerk.Framework.FrameworkSettings:ServiceAdapter:InvokeMethod ("":U,
                                                                                THIS-OBJECT:EntityName,
                                                                                "PutCustomerOnHold":U,
                                                                                INPUT-OUTPUT DATASET dsCustomer,
                                                                                poParameter) .
    ELSE 
        Consultingwerk.OERA.ServiceInterface:InvokeMethod (THIS-OBJECT:EntityName,
                                                           "PutCustomerOnHold":U,
                                                           INPUT-OUTPUT DATASET dsCustomer,
                                                           poParameter) .
    {Consultingwerk/foreachABL.i Consultingwerk.OERA.TableModel oTable in THIS-OBJECT:TableModels}
        IF VALID-HANDLE (oTable:QueryHandle) THEN DO:
            oTable:QueryHandle:QUERY-OPEN () .
            oTable:QueryHandle:GET-FIRST () .
        END. 
    END.
        
END METHOD .

Invoking the method through the DatasetModel class instance

Sample of invoking the method though the DatasetModel instance
oCustomer = NEW CustomerDatasetModel () .
oCustomer:PutCustomerOnHold (NEW PutOnHoldParameter (7, "Urgent!")) .
BufferHelper:ShowBuffer (oCustomer:DatasetHandle::eCustomer) .

Templates for proxy methods

The "template" parameter to the @InvokeMethod defines which code template should be used when creating the template method.

The default template name (when the template parameter is missing) is: "invoke-receive-dataset". Templates are expected to be located in the "DatasetModelInvokeMethods" sub folder of the Business Entity Designer template folder (e.g. Consultingwerk\BusinessEntityDesigner\Generator\Templates).

The following templates are provided by Consultingwerk:

Template nameDescription
invoke-receive-dataset (default)

Invokes the method in the Business Entity. Does not pass the current dataset contents from the DatasetModel instance to the method.

The dataset is emptied before calling. When the method returns, the data which was returned from the Business Entity method replaces the data in the DatasetModel instance so that the updated/new data is available in the dataset model instance.

invoke-no-dataset

Invokes the method in the Business Entity. Does not pass the current dataset contents from the DatasetModel instance to the method.

When the method returns, the data which was returned from the Business Entity method is not stored. The data in the DatasetModel instance keep unchanged when calling the Business Entity method.

Customers can create custom method templates if needed.

In the custom template you can use three placeholders:

PlaceholderDescription
&1Will be replaced with the method name.
&2Will be replaced with the parameter class type of the second parameter of the method. 
&3Will be replaced with the name of the dataset.