JDito API
LoadEntity and WriteEntity are core ADITO mechanisms for working with datasets. These terms do not refer to single method names. Instead, they describe a group of methods that load or write data through an Entity.
This means that the primary target is not the database directly, but the Entity and its Fields, including customized logic such as values, display values, validations, and dependencies. Database access still happens underneath, but calculated EntityFields without direct database mapping can also be handled through this mechanism.
Use LoadEntity and WriteEntity primarily if you need to
- load or write datasets while strictly respecting the permissions configured by the client administrator,
- work with Entities that are based on multiple database tables without writing SQL joins manually,
- load calculated EntityFields,
- load the display value of an EntityField,
- respect presets, validations, and dependencies during loading and writing, or
- benefit from caching through RecordContainerCache.
- Use LoadEntity and WriteEntity as the preferred approach whenever permissions must be respected.
- Other loading and writing mechanisms such as
SqlBuilderordb.xxxignore the permissions configured by the client administrator. This can create serious security issues because sensitive data may become visible to unauthorized users.
However, LoadEntity and WriteEntity introduce more overhead than direct SQL operations. If the code is executed frequently inside loops or recurring processes and requires maximum performance, these mechanisms may not be the best option. Calls inside Entity processes such as onValidation, stateProcess, valueProcess, or displayValueProcess require particular care because
- Entity processes are executed during loading,
- a count query is executed during loading, and
- larger result sets can consume substantial RAM.
Example: If you use LoadEntity in the validation of a Consumer input, the already persisted related data is loaded during the Consumer onValidation process. Because this process runs very frequently during editing, even a single Entity load that takes half a second or more can become expensive. If only a small amount of data is needed and permissions do not matter in that situation, a direct SQL query is usually the better choice.
To use LoadEntity or WriteEntity methods, import:
import { entities } from "@aditosoftware/jdito-types";
The JSDoc of these methods, accessible via CTRL+SPACE, is not yet complete. It will be extended in a future ADITO version.
LoadEntity
LoadEntity covers the methods used to retrieve datasets through an Entity.
The first step is to create a configuration object. Two variants are available for different use cases:
// 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 chainable setter methods for defining the datasets to load, similar to SqlBuilder. The order of the method calls does not matter. Use code completion after a trailing . to inspect the available functions.
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 |
For .uids, an empty array means that nothing is loaded, so getRows returns an empty array and getRowCount returns 0. By contrast, null means that there is no UID-related restriction at all, which is equivalent to not calling this setter method.
| 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. This affects both result handling and the filling of variables such as sys.uid; see 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 (module activity) 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.