Implementing CRUD Wrappers — Saving Data

Justin James
5 min readFeb 19, 2020

Whew! We are almost there, I promise! Let’s work on two sample Entities to show how we build them out, and then get data saved to them. They will be called SampleEntity and SampleEntityFK. As always, the reference code is the Application Framework on the OutSystems Forge!

How do I get this data into the system? Bueller? Bueller?

Preparation

First, let’s make sure that your application is organized correctly. Are you following Four Layer Canvas, or at least something close to it (it’s often difficult to be 100% aligned with it)? Good. In your Model, we are going to make a folder under Logic, with the same name as your Entity (“SampleEntity”). Next, let’s create SampleEntity with the following fields:

  • Id (the default Id field created by OutSystems)
  • Name (Text 50, Mandatory)
  • CreatedOn (DateTime, Mandatory)
  • CreatedByUserId (User Identifier, Mandatory)
  • UpdatedOn (DateTime, Mandatory)
  • UpdatedByUserId (User Identifier, Mandatory)
  • IsActive (Boolean, Mandatory)

Now, in the Entity’s “More” properties, open “More Options” and set “Label” and “Order By” to “Name”, and “Is Active” to “IsActive”. Finally, set the Entity to Public = “Yes” and “Expose Read Only” to “Yes”.

Copy SampleEntity, and rename the copy to be “SampleEntityFK”. Change the Id from an autonumbering Long Integer, to type “SampleEntity Identifier”. This Entity represents the Extension Entity pattern.

We will also want a few Exceptions to be made: ValidationException and ProcessingException.

Saving Data

Create a folder under “Logic” named “SampleEntity”. Next, let’s make an Action called SampleEntity_Upsert within that folder. This action has an input called “Source” of type SampleEntity (the Record itself), with an output of Id of type SampleEntity Identifier, and another output called EntityActionResult with that type. “Upsert” combines “Insert” with “Update”; in other words, it handles the “Create” and “Update” functions. Why not keep them separate like the Entity Actions do? You could if you really wanted to, but in the seven years I have been following this pattern, never once have I found a need to do that, except for in situations where a “Create” operation was so different from a basic “save” that it was more like a “Generate” Action that would take a set of parameters and determine how to create the record.

Note: sharp-eyed readers will notice that in these Actions, we are not using our standing naming conventions for the inputs and outputs. The reason for this is to 100% match the parameter naming of the built-in Entity Actions so these can be a perfect drop-in replacement for them once they are built.

The SampleEntity_Upsert needs an All Exception handler, which does the following:

  • Sets the Id output parameter to NullIdentifier()
  • Calls BuildEntityActionResultFromFailure, passing in the message from the Exception, and assigns the output to the EntityActionResult output parameter

It will also have a Database Exception handler:

  • Sets the Id output parameter to NullIdentifier()
  • Calls BuildEntityActionResultFromFailure, passing in a generic message such as “An error has occurred, please contact your system administrator” (to prevent database details from being leaked to a potential attacker, and not give users a strange database-specific error message)

Next, we start building the logic in this order:

  1. Validation logic. The “quick and dirty” on validation is that any failed validation can throw a ValidationException with a sensible message that will be friendly to the end-users of the application. Alternatively, you can have an EntityActionResultMessage variable, set it’s value to the validation message, append it to the EntityActionResult.EntityActionResultMessages list, set EntityActionResult.IsSuccess to False, and at the end of the Upsert action, make a call to EntityActionMessages_Combine() and set the combined valued of EntityActionResult to its output. This is more effort, but will allow you to give the user the full list of failures in one save attempt, rather than them trying to save, getting a failure, fixing it, getting another failure, and so on. You may want to consider extracting the validation to a separate Action to be called by the Upsert as well as anywhere else you may need to; I almost never have felt the need for this, but it is a very good idea that may make sense for you.
  2. Set any default values.
  3. Have an If widget, that looks at Source.Id = NullIdentifier() and branches. The True branch will set CreatedOn = CurrDateTime(), CreatedByUserId = GetNormalizedSessionUserId(), UpdatedOn = Source.CreatedOn (I do this to guarantee that they are identical), UpdatedByUserId = Source.CreatedByUserId (faster than a second call to GetNormalizedSessionUserId()), and Source.IsActive = True (because no one should ever be creating a new record as soft-deleted, we do not want the consumers of the code to have to remember this step when it is not relevant to them). Then it calls CreateSampleEntity (more performant than CreateOrUpdateSampleEntity), and sets Id to the output. The False branch just sets UpdatedOn =CurrDatetime() and UpdatedByUserId = GetNormalizedSessionUserId(), and sets Id = Source.Id.
  4. At this point the two branches re-join, set EntityActionResult to BuildEntityActionResultFromSuccess, passing in a nice and friendly message (such as: “SampleEntity “”” + Source.Name + “”” has been saved.”).
  5. Finally, any post-saving cleanup occurs, such as adjusting rollup values in parent records.
Implementation of SampleEntity_Upsert

Now, we copy the “SampleEntity” folder in Logic and paste it, then re-name it to be SampleEntityFK. Rename the Action in it to be SampleEntityFK_Upsert. The only change to be made in the SampleEntityFK_Upsert Action, is in the “If” that checks “Source.Id = NullIdentifier()”, we change that to say “Source.CreatedOn = NullDate() and Source.CreatedByUserId = NullIdentifier()”. You will also want to add a validation to ensure that Source.Id is not NullIdentifier(). Remember, this is an Extension Entity pattern, so we require the Id to be populated by the application logic before trying to save it.

Wrapup

The next entry in this series on CRUD Wrappers will show us how to remove data. And after that? We will build out the option read wrappers.

J.Ja

--

--