LoadEntity and WriteEntity
LoadEntity and WriteEntity are essential functionality of the ADITO platform, used to manage datasets/records. The naming of these terms is not related to method names, but they summarize functionality represented by several methods used for loading and writing data from/into an Entity.
This means, the loading/writing does not, in the first place, target the database, but the Entity and its Fields - respecting customized logic for values, displayValues, validations, etc. Of course, at last, the database will be accessed via the Entity, but also calculated EntityFields, without relation to the database, can be accessed.
You primarily use LoadEntity and WriteEntity if you want to
- load and write datasets strictly according to the permissions (access rights) configured by the client administrator - which, e.g., is not the case when loading data via SqlBuilder or the db.xxx methods (!).
- load or write data which is related to an Entity that is based on more than one database table (no need to care for SQL joins etc.).
- load a calculated EntityField
- load the displayValue of an EntityField
- have an EntityField's presets, validations, and dependencies to be respected in the loading/writing logic
- utilize data caching via a RecordContainerCache.
- LoadEntity and WriteEntity should be your preferred way to load and write data in ADITO, when permissions (access rights) must be respected.
- Whenever you use other loading/writing options (e.g., SqlBuilder, db.xxx methods, etc.), the permissions configured by the client administrator will be ignored, which may cause severe data security leaks, as critical data may be disclosed to unauthorised persons.
However, performance issues can be involved, as LoadEntity and WriteEntity include more overhead and calculations than an SQL select or insert. Therefore, it is important to be careful when utilizing LoadEntity and WriteEntity: If the code is frequently executed within loops or recurring processes and requires optimal speed, LoadEntity and WriteEntity may not be the most suitable options. Particularly in Entity processes, LoadEntity and WriteEntity calls should be handled carefully, e.g., in onValidation, stateProcess, valueProcess, or displayValueProcess. Problems can occur, because
- processes of the Entity are executed when loading
- a count is executed when loading
- When larger data amounts are involved, LoadEntity consumes extensively RAM.
Example: If you use LoadEntity for validation of input of a Consumer, then the already saved connected data will be loaded via LoadEntity in the onValidation process of the Consumer. This process will be executed very often during editing, and one Entity loading can take from 0,5 to up to multiple seconds. In this case, the required data is very small, and in most cases there is no need for respecting permissions. Thus, an SQL select would do the same job in a fraction of the time LoadEntity requires.
In order to use any method of LoadEntity or WriteEntity, we need the following import:
import { entities } from "@aditosoftware/jdito-types";
The documentation (JSDoc) of each method (available by using CRTL+SPACE) is not finished yet. It will be available in a future ADITO version.
LoadEntity
The term LoadEntity summarizes the methods to get datasets related to an Entity.
The first step is to create a configuration object. There are 2 types of configuration objects available, specific for different purposes:
// configuration for loading datasets
// (return value: Object of type "LoadRowsConfig")
var myConfig1 = entities.createConfigForLoadingRows()
// configuration for loading datasets from an Entity via a Consumer
// (return value: Object of type "LoadConsumerRowsConfig")
var myConfig2 = entities.createConfigForLoadingConsumerRows()
These configuration objects provide setter methods (parameters) that can be chained in order to define the datasets that should be loaded (similar to the chaining approach of, e.g., SqlBuilder). The order of the method calls does not matter. To see how to add these parameters, please refer to the example below. By adding a "." to the end of the configuration you can use the code completion to see all available functions by using CTRL+SPACE.
All setter methods (parameters) available for LoadEntity:
| Setter Method | Description |
|---|---|
.entity (String) | the name of the Entity whose datasets are to be loaded |
.fields (Array) | list of EntityFields of the Entity. If you specify here only "#UID", then the ADITO system tries to optimize the loading process (e.g., by skipping unnecessary processes) |
.filter (String) | filter to be applied when loading the datasets |
.count (Number) | maximum number of datasets |
.provider (String) | name of the Provider that is to be used to retrieve the data |
.addParameter (String, String) | specifies a Parameter, to be used to supply a Provider. The first argument of this method is the name of the Parameter (e.g., "ContactId_param"), the second argument is the value to be assigned to this Parameter, e.g., a UID). |
.startRow (Number) | number of the row of the datasets to start loading |
.uid (String) | UID of the dataset to be loaded |
.uids (Array) | UIDs of the datasets to be loaded |
If the argument of this setter method is:
- an empty Array, then nothing is loaded (subsequent method
getRowsreturns an empty Array, and methodgetRowCountreturns 0) null, then there is not any UID-related restriction at all (= same as if this setter method was not executed at all)
| Setter Method | Description |
|---|---|
.user (String) | title of the user (e.g. "Harold Smith"), in whose "user context" (permissions, calendar or mail settings, etc.) the loading logic will be executed. For reasons of data security, this works only in server processes. (In client-related processes, it will cause an error.) |
.ignorePermissions() | Load without respecting the permissions of the respective user (but other user-specific functionality, e.g., calendar or mail settings, do still apply). |
The methods .entity and .fields are mandatory. If these are not used, the configuration is invalid. You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
The configuration can be executed via 3 different methods, which have different purposes.
The executing methods of LoadEntity:
| Setter Method | Description |
|---|---|
| Method | Description |
entities.getRow (LoadRowsConfig) | returns a single row (datasets) |
entities.getRows (LoadRowsConfig) | returns all rows (datasets) |
entities.getRowCount (LoadRowsConfig) | returns the number of rows (datasets) |
(The methods for LoadConsumerRowsConfig are the same.)
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
The methods entities.getRow and entities.getRows differ in their behavior, which has an effect especially on the processing of the results and the filling of variables like sys.uid - see chapter getRow vs. getRows.
Benefits
Using LoadEntity shows the following advantages compared to loading data directly via SqlBuilder or the db.xxx methods:
- LoadEntity respects the permissions (access rights) configured by the client administrator. Nevertheless, if required, you can skip the permissions, by adding
.ignorePermissions(). - Complex SQL queries (with JOINs, subselects, etc.) can be avoided - e.g., in cases when an Entity is related to more than one single database table.
- An EntityField's presets and dependencies are respected in the loading logic.
- The data is loaded via the RecordContainer of the Entity; thus, all data can be cached - which results in a better user experience and faster response times when using programs like, e.g., Apache Ignite.
- Every EntityField of the Entity can be loaded, even if this EntityField is not directly related to one specific database field (e.g., a calculated EntityField).
Example
Below you find an example code of a test Action that loads all datasets of the xRM project's Entity Activity_entity and logs the result in detail. The loading is restricted to the values of the fields SUBJECT, INFO, ENTRYDATE, and the display values of the fields DIRECTION and RESPONSIBLE. You can always reduce the size of the result set by filtering according to values or entering UIDs.
// creating the configuration object
var config = entities.createConfigForLoadingRows();
// setting the Entity's name
config.entity("Activity_entity");
// defining the required EntityFields
config.fields([
"SUBJECT",
"INFO",
"DIRECTION.displayValue",
"ENTRYDATE",
"RESPONSIBLE.displayValue"
]);
// optional restriction to 1 UID
// config.uid("0cf02b72-a46a-4cd2-975f-15556618ea90");
// optional restriction to multiple UIDs
// config.uids(["0cf02b72-a46a-4cd2-975f-15556618ea90",
// "21852330-9c66-42a3-9d25-d053833f146d"]);
var myResult = entities.getRows(config);
// Retrieving a summary of each dataset
for (let i in myResult) {
logging.log("-----> Dataset number " + i + ":")
logging.log(myResult[i]);
}
// Retrieving the single values of specific EntityFields
for (let i = 0; i < myResult.length; i++) {
logging.log("-----> Dataset number " + i);
// each part of the result is an associative array
logging.log("SUBJECT = " + myResult[i]["SUBJECT"]);
logging.log("INFO = " + myResult[i]["INFO"]);
logging.log("DIRECTION = " + myResult[i]["DIRECTION.displayValue"]);
logging.log("ENTRYDATE = " + myResult[i]["ENTRYDATE"]);
logging.log("RESPONSIBLE = " + myResult[i]["RESPONSIBLE.displayValue"]);
}
MyTest_entity.testAction1a.onActionProcess
Variation: Example of loading only 1 specific dataset via entities.getRow(config).
var config = entities.createConfigForLoadingRows();
config.entity("Activity_entity");
config.fields([
"SUBJECT",
"INFO",
"DIRECTION.displayValue",
"ENTRYDATE",
"RESPONSIBLE.displayValue"
]);
// Restriction to 1 UID
config.uid("0cf02b72-a46a-4cd2-975f-15556618ea90");
var myResult = entities.getRow(config);
// Retrieving each field of the dataset
for (let i in myResult) {
// e.g., myResult["SUBJECT"]
logging.log("-----> Dataset index = " + i + ": " + myResult[i]);
}
MyTest_entity.testAction1b.onActionProcess
Please note that, in this case, the result is 1 single object, which you can directly access as associative array, e.g., like this: myResult["SUBJECT"]. Furthermore, note that entities.getRow(config) requires a configuration that restricts the result to one single dataset. Otherwise, you will get an exception.
getRow vs. getRows
The methods entities.getRow and entities.getRows differ in their behavior, which has an effect especially on the processing of the results and the filling of variables like sys.uid.
entities.getRow:
- This method loads a specific dataset.
- Variables like
sys.uidare automatically filled with the values of the loaded dataset. - If the requested dataset cannot be found, an exception is thrown, which must explicitly be caught by an individual error handling.
- The behavior is similar to opening a PreviewView or MainView.
entities.getRows:
- Here, multiple datasets are returned, based on a filter.
- Variables refering to single datasets, like
sys.uid, are not filled. - If no datasets are found, no exception will be thrown - even not in case only one single dataset was expected.
- The behavior is similar to the loading of a FilterView.
Consequences in practice:
- Specific data handling: If it is required that variables like
sys.uidare filled, thenentities.getRowshould be used. In this case, you need to make sure that possible exceptions (caused, e.g., by not findable datasets) are handled appropriately. - Exception-tolerant queries:
entities.getRowsshould be used for queries with no exact number of hits to be guaranteed or expected, in order to avoid exceptions and to keep results flexible.
Example:
Here is an example code to be used with entities.getRow, covering every error case:
var conf = entities.createConfigForLoadingRows()
.entity("Person_entity")
.uid("38cb4fab-64f9-4d8e-aa6f-a158d13fc933")
.fields(["#CONTENTTITLE"]);
try {
var myRow = entities.getRow(conf);
log.info("Dataset successfully loaded: " + myRow["#CONTENTTITLE"]);
// process dataset
(...)
}
} catch (exception) {
// Exception handling (can be adapted to requirements individually)
log.error("Error when loading dataset: " + exception.message);
// Specific action in case of an exception
// e.g., setting standard values, informing the user,
// or cancelling the operation
(...)
}
WriteEntity
The term WriteEntity summarizes the methods to write datasets "into" an Entity, and thus, into the database table(s) related to it (create, update, or delete records).
The first step is to create a configuration. There are 3 types configurations available, specific for different purposes:
// configuration for creating new datasets
var myConfig = entities.createConfigForAddingRows()
// configuration for updating new datasets
var myConfig = entities.createConfigForUpdatingRows()
// configuration for delecting new datasets
var myConfig = entities.createConfigForDeletingRows()
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
These configuration objects provide setter methods (parameters) that can be chained in order to define the datasets that should be loaded (similar to the chaining approach of, e.g., SqlBuilder). The order of the method calls does not matter. To see how to add these parameters, please refer to the example below. By adding a "." to the end of the configuration you can use the code completion to see all available functions by using CTRL+SPACE.
Depending on the configuration type, there are different parameters available:
All setter methods (parameters) available for create configuration (createConfigForAddingRows()):
| Setter Method | Description |
|---|---|
| Setter Method | Description of arguments |
.entity (String) | the name of the Entity whose datasets are to be written |
.fieldValues (Array) | an Array of the EntityFields or Consumers, along with their values (Important: Mind the order! See information box further below.) |
If the values are restricted by a value list (via dropDownProcess or a Consumer) there is no validation, i.e., the values are written as given, even if they are not included in the value list. If you need a validation, use onValidation.
| Setter Method | Description |
|---|---|
.consumer (String) | name of the Consumer that is to be used to write the data |
.provider (String) | name of the Provider that is to be used to write the data |
.addParameter (String, String) | specifies a Parameter, to be used to supply a Provider or a Consumer. The first argument of this method is the name of the Parameter (e.g., "ContactId_param"), the second argument is the value to be assigned to this Parameter, e.g., a UID). |
.user (String) | title of the user (e.g. "Harold Smith"), in whose "user context" (permissions, calendar or mail settings, etc.) the create logic will be executed. For reasons of data security, this works only in server processes. (In client-related processes, it will cause an error.) |
.ignorePermissions() | Write without respecting the permissions of the respective user (but other user-specific functionality, e.g., calendar or mail settings, do still apply). |
For the create configuration, the methods .entity and .fieldValues are mandatory. If these are not used, the configuration is invalid.
When writing the Array-typed argument of method .fieldValues, please urgently consider the correct order of the EntityFields, as the ADITO platform will process the EntityFields exactly in the given order. This is crucial, if one EntityField is logically dependend on another EntityField - e.g., if the valueProcess of MYENTITYFIELD2 contains the code vars.get("$field.MYENTITYFIELD1"), then, in the Array, MYENTITYFIELD1 must necessarily be specified before MYENTITYFIELD2. Otherwise, the required value of MYENTITYFIELD1 will not yet be set when vars.get is called.
This behavior no bug, but intended, because WriteEntity should work like a user works in the client: If, e.g., users call an Action without filling in the value of a dependent EntityField before, they will also not get the intended result.
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
All setter methods (parameters) available for update configuration (createConfigForUpdatingRows()):
| Setter Method | Description |
|---|---|
| Setter Method | Description |
.entity (String) | the name of the Entity whose datasets are to be written |
.uid (String) | UID of the dataset to be updated |
.fieldValues (Array) | an Array of EntityFields or Consumers, along with their values (Important: Mind the order! See information box above.) |
If the values are restricted by a value list (via dropDownProcess or a Consumer) there is no validation, i.e., the values are written as given, even if they are not included in the value list. If you need a validation, use onValidation.
| Setter Method | Description |
|---|---|
.consumer (String) | name of the Consumer that is to be used to update the data |
.provider (String) | name of the Provider that is to be used to update the data |
.addParameter (String, String) | specifies a Parameter, to be used to supply a Provider or a Consumer. The first argument of this method is the name of the Parameter (e.g., "ContactId_param"), the second argument is the value to be assigned to this Parameter, e.g., a UID). |
.user (String) | title of the user (e.g. "Harold Smith"), in whose "user context" (permissions, calendar or mail settings, etc.) the update logic will be executed. For reasons of data security, this works only in server processes. (In client-related processes, it will cause an error.) |
.ignorePermissions() | Write without respecting the permissions of the respective user (but other user-specific functionality, e.g., calendar or mail settings, do still apply). |
For the update configuration, the methods .entity, .fieldValues, and .uid are mandatory. If these are not used, the configuration is invalid.
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
All setter methods (parameters) available for delete configuration (createConfigForDeletingRows()):
| Setter Method | Description |
|---|---|
| Setter Method | Description |
.entity (String) | the name of the Entity whose datasets are to be written |
.uid (String) | UID of the dataset to be updated |
.provider (String) | name of the Provider that is to be used to delete the data |
.addParameter (String, String) | specifies a Parameter, to be used to supply a Provider. The first argument of this method is the name of the Parameter (e.g., "ContactId_param"), the second argument is the value to be assigned to this Parameter, e.g., a UID). |
.user (String) | title of the user (e.g. "Harold Smith"), in whose "user context" (permissions, calendar or mail settings, etc.) the delete logic will be executed. For reasons of data security, this works only in server processes. (In client-related processes, it will cause an error.) |
.ignorePermissions() | Load without respecting the permissions of the respective user (but other user-specific functionality, e.g., calendar or mail settings, do still apply). |
For the delete configuration, the methods .entity and .uid are mandatory. If these are not used, the configuration is invalid.
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
Depending on the purpose (and thus, on the configuration), there are the following execute methods.
| Function | Description |
|---|---|
entities.createRow (CreateRowConfig) | creates a new dataset and returns its UID |
entities.updateRow (UpdateRowConfig) | updates the dataset |
entities.deleteRow (DeleteRowConfig) | deletes the dataset |
The execute methods of WriteEntity
You can see the documentation (JSDoc) of each method by using CRTL+SPACE.
Benefits
Using WriteEntity shows the following advantages compared to writing, updating, or deleting data directly via SqlBuilder or the db.xxx methods:
- WriteEntity respects the permissions (access rights) configured by the client administrator. Nevertheless, if required, you can skip the permissions, by adding
.ignorePermissions(). - Complex or multiple SQL queries can be avoided - e.g., in cases when an Entity is related to more than one single database table.
- Updated or deleted records can be cached, which results in a better user experience and faster response times when using programs like, e.g., Apache Ignite.
- An EntityField's presets, validations, and dependencies are respected in the writing logic.
- No need for subsequently executing refresh logic (like
neon.refreshAll()). This means, e.g., in a "Table" ViewTemplatedeleteRowonly deletes the respective datasets - no refreshing/reloading of all datasets required.updateRowautomatically reloads (only) the respective datasets.
- Every EntityField of the Entity can be loaded, even if this EntityField is not directly related to one specific database field (e.g., a calculated EntityField).
- Encapsulation with configurations.
Examples
Example 1:
Below you find an example code of a test Action that creates an Activity dataset, without ActivityLinks.
// creating the configuration object
var config = entities.createConfigForAddingRows();
// name of the Entity
config.entity("Activity_entity");
// mapping of the EntityFields and their values
config.fieldValues({
"SUBJECT": "Test Activity",
"INFO": "This is some demo information",
"DIRECTION": "o",
"ENTRYDATE": datetime.date().toString(),
"CATEGORY": "MAIL"
});
// execution method for creating a new dataset
var id = entities.createRow(config);
// loggin the automatically created UID of the new dataset,
// e.g., "38cb4fab-64f9-4d8e-aa6f-a158d13fc978"
logging.log("ACTIVITYID: " + id);
MyTest_entity.testAction2a.onActionProcess
After executing this Action's code, the onDBInsert process of the given Entity will be executed.
Note that you can re-use config objects, e.g., if you want to create multiple similar datasets and the config is the same except for the respective ID. The following example shows how to insert one Activity dataset along with multiple ActivityLinks to various Projects.
// IDs of the projects ot be linked to the Actitvity
var projectIds = ["c702e624-6675-4841-ac98-38da133a1c5b",
"559646cf-dcbf-4171-b251-952ac2ab9100",
"4436f590-1adb-466f-aad2-2cba0174aad7",
"9c78b5a2-36ee-45dd-9543-9099b78d28f2",
"029e0150-87bc-4f3a-9d34-7c455201f246"];
// config for creating the Activity dataset
var config = entities.createConfigForAddingRows();
config.entity("Activity_entity");
// config for creating ActivityLink datasets
var configLink = entities.createConfigForAddingRows();
configLink.fieldValues({
"OBJECT_TYPE": "Salesproject"
}
);
// field values for creating the Activity dataset
config.fieldValues({
"SUBJECT": subject,
"TYPE": "LETTER",
"ENTRYDATE": datetime.date().toString(),
// link to configLink object
"Links": [configLink]
}
);
// createRow is executed multiple times via a loop,
// each loop cycle with the same configLink object,
// but with different projectId values
for (let projectId of projectIds) {
configLink.fieldValues({
"OBJECT_ROWID": projectId
}
);
entities.createRow(config);
}
MyTest_entity.testAction2b.onActionProcess
Example 2:
Below you find an example code of a test Action that updates an existing Activity dataset.
// creating the configuration object
var config = entities.createConfigForUpdatingRows();
// name of the Entity
config.entity("Activity_entity");
// mapping of the fields to be updated
config.fieldValues({
"SUBJECT": "My new Subject value",
"DIRECTION": "i"
});
// UID of the dataset to be updated
config.uid("38cb4fab-64f9-4d8e-aa6f-a158d13fc978");
// execution method for updating a dataset
entities.updateRow(config);
MyTest_entity.testAction3a.onActionProcess
After executing this Action's code, the onDBUpdate process of the given Entity will be executed.
Also for updates, you can re-use config objects, e.g., if you want to update multiple datasets, with the config being the same except for the respective ID. The following example shows how to set 3 different Persons (identified by their CONTACTID) inactive.
// CONTACTIDs of Person datasets to be set inactive
var contactIdsToUpdate = ["4c9e95fe-25ae-4875-bd84-7b3705edd4fa",
"27596cb7-2211-429b-801f-b428250496e8",
"6263b12a-b19c-4870-97a4-1f044fe102e5"];
// one config for all changes
var config = entities.createConfigForUpdatingRows();
config.entity("Person_entity");
config.fieldValues({
"STATUS": "CONTACTSTATINACTIVE"
});
// updateRow is executed multiple times via a loop,
// each loop cycle with the same config object,
// but with different CONTACTID values
for (let idToUpdate of contactIdsToUpdate) {
config.uid(idToUpdate);
entities.updateRow(config);
}
MyTest_entity.testAction3b.onActionProcess
Example 3:
Below you find an example code of a test Action that deletes an existing Activity dataset.
// creating the configuration object
var config = entities.createConfigForDeletingRows();
// name of the Entity
config.entity("Activity_entity");
// UID of the dataset to be deleted
config.uid("38cb4fab-64f9-4d8e-aa6f-a158d13fc978");
// execution method for deleting a dataset
entities.deleteRow(config);
MyTest_entity.testAction4.onActionProcess
Before (!) executing this Action's code, the onDBDelete process of the given Entity will be executed.
Example 4:
Below you find an example code of a test Action that creates an Activity dataset, along with ActivityLink datasets (linking the Activity to other Entities). As you can see, you can encapsulate multiple configurations with WriteEntity.
// encapsulated configuration for link1
var configLink1 = entities.createConfigForAddingRows();
// field mapping
configLink1.fieldValues({
// "field" : "value"
"OBJECT_TYPE": "Person",
"OBJECT_ROWID": "c7ddf982-0e58-4152-b82b-8f5673b0b729"
});
// encapsulated configuration for link2
var configLink2 = entities.createConfigForAddingRows();
// field mapping
configLink2.fieldValues({
"OBJECT_TYPE": "Organisation",
"OBJECT_ROWID": "6efb4fab-64f9-4d8e-aa6f-a158d13fc273"
});
// now create a new Activity with ActivityLinks
// creating the configuration object
var config = entities.createConfigForAddingRows();
// name of the Entity
config.entity("Activity_entity");
//field mapping
config.fieldValues({
"SUBJECT": "My Linked Activity",
"INFO": "This is some demo information",
"DIRECTION": "o",
"ENTRYDATE": datetime.date().toString(),
"CATEGORY": "MAIL",
// connect the configurations
// via Activity_entity's Consumer "Links"
"Links": [configLink1, configLink2]
});
// execution method for creating a new dataset
var id = entities.createRow(config);
// loggin the automatically created UID of the new dataset,
// e.g., "88ae4fab-64f9-4d8e-aa6f-a158d13fd132"
logging.log("ACTIVITYID: " + id);
MyTest_entity.testAction5.onActionProcess
After executing this Action's code, the onDBInsert process of the given Entity will be executed.
Usage in server processes
LoadEntity and WriteEntity can also be used in server processes. However, if you use it there, a user must be assigned. If required, simply create a "technical user" for that purposes, i.e., a user dataset that is not related to a real person but only to be used by specific internal logic.
Skipping prevalidation
Every of the Entity configs provides the .skipPrevalidation(Boolean) setter method. The default value within the config is false. In this default state the changes get validated before the Entity is saved. This prevents incomplete entries from being saved. If you set the value to true, validations are performed when saving data.
Using this method may be necessary when writing or changing at lot of data via processes as it reduces the number of validations and may lead to an increased performance.
If you skip the prevalidation, you have to make sure your data is correct. Otherwise it may fail validation checks and incomplete data might be saved.