BDS Operations
Introduction
The Business Data Storage provides a set of operations that can be used by FNZ Studio developers to manage the persistence of Data Class instances to the Database model produced by the Synchronization algorithm.
These operations are available to the FNZ Studio developer through built-in script functions. For details on those, see the BDS Script Functions chapter.
It is recommended that you always use the built-in Script Functions to execute CRUD operations. For example, it is not recommended that you execute manual inserts on the Database using SQL statements or data import tools. If such manual operations cannot be avoided, always make sure to use the BDS Database Sequence defined as a default value of the persistence_id column of Main Tables to generate the content of such column.
The operations provided by the Business Data Storage are:
- Save: persists one (or more) Data Class instance(s) to the Database for the first time ('Create' operation) or updates it if it already exists in the Database ('Update' operation).
- Load: reads one (or more) Data Class instance(s) from the Database.
- Delete: deletes one (or more) Data Class instance(s) from the Database.
- A Data Class instance that has been saved to the Database is associated to a persistence ID. This ID uniquely identifies the Data Class instance across the whole Database (for more details see the Main Table section of the Database Model Synchronization chapter).
- When persisting a Data Class instance, the Save operation checks whether a persistence ID is associated with the instance. If so, it performs an 'Update' operation, otherwise it performs a 'Create' operation.
- The Load and Delete operations use the persistence ID to find the right Data Class instance to operate on in the Database.
- The persistence ID is saved in a metadata attribute of the Data Class instance called BusinessDataStorage.PersistenceId.
- The Business Data Storage provides built-in script functions to get and remove the persistence ID of a Data Class instance. For further details, see the Script Functions chapter.
Cascading Operations
When loading a Data Class instance, it is sometimes useful to decide which related Data Class instances referenced in (complex) properties of the source instance should be loaded together with it. The same applies when saving and deleting Data Class instances.
The BDS provides the FNZ Studio developer with a way to define which properties of a Data Class the operations should automatically be propagated to: this is called cascade mode.
There are 4 modes of cascading:
- None: no operation is cascaded.
- Load: the Load operation is cascaded.
- Save: the Load and Save operations are cascaded.
- Delete: the Load, Save and Delete operations are cascaded.
It is possible to specify one of these cascade modes for each CV (Complex Value) and CCV (Collection of Complex Values) property of each stored Data Class (for a detailed explanation of the different property types, see the Database Model Synchronization chapter). This setting is then used by the Save, Load and Delete operations to decide whether to automatically cascade to the target Data Class instances or not.
The default cascade mode of CV and CCV properties is Load, so by default all properties are loaded in cascade, while Save and Delete operations are performed on individual Data Class instances. The cascade mode setting has no effect on the structure of the Database model (tables, columns).
Note that the cascade settings only control how to handle the target Data Class instances. The relationships (associations) among Data Class instances are processed by the Save operation regardless of the cascade setting.
In addition, all operations take an additional cascade parameter as an input:
- If the operation is invoked with
cascade = false
, it is only executed on the current Data Class instance (and on the relationships to the target Data Class instances, but not on the target Data Class instances themselves). - If the operation is invoked with
cascade = true
, it is also propagated to all Data Class instance targets of properties with the relevant cascade mode, recursively.
Example
Given the following FNZ Studio data model and cascade setting:
Person DataClass
- String name
- Address address (cascade mode = Save)
- Person bestFriend (cascade mode = Load)
Address DataClass
- String city
The following behavior is obtained:
- when loading a Person with
cascade = true
, the corresponding Address (if any) is loaded as well. - when saving a Person with
cascade = true
, the corresponding Address (if any) is saved as well. - when deleting a Person with
cascade = true
, the corresponding Address (if any) is not deleted. - when loading a Person with
cascade = true
, the corresponding best friend Person (if any) is loaded as well. - when saving a Person with
cascade = true
, the corresponding best friend Person (if any) is not saved. However, if a new best friend Person is set or removed, the association between the two Data Class instances is updated accordingly in the Database (i.e. the target persistence ID is updated in the appropriate column of the Person main table). - when deleting a Person with
cascade = true
, the corresponding best friend Person (if any) is not deleted.
Note that all cascade settings are entirely optional and can be ignored if it is not necessary to optimize the way Data Class instances are loaded, saved or deleted.
Data Placeholder
The Data Placeholder is a marker that allows the FNZ Studio developer to identify the CV and CCV properties of a Data Class instance that were not loaded from the Database due to cascade settings. This is useful to distinguish empty properties (null value) from those properties that do not have a value because it was not loaded from the Database. In case of non-cascaded CCV properties, a single Data Placeholder is used to mark the whole collection.
This is how it works:
- When the BDS loads a Data Class instance from the Database, and it finds a property that should not be loaded (cascading not requested), it populates the property with a Data Placeholder object instead of leaving it null.
- When the Data Class instance is then saved back to the Database, the BDS skips the property marked by the Data Placeholder, thus preserving its previous state.
- If a new value is assigned to a property containing a Data Placeholder, the new value is used to overwrite the one in the Database when saving back the Data Class instance. However, every attempt to use the Data Placeholder as if it was the actual content (e.g. showing the value of the property in a screen) fails since the data is still in the Database and not available to FNZ Studio.
The BDS provides a built-in script function to check if a property contains a Data Placeholder.
Whenever needed, it is also possible to load the content of a property containing a Data Placeholder from the Database by using the built-in Script Function BDSLoadDataPlaceholderContent.
Save Operation
The Save operation works in the following way:
- The Save operation receives, as an input, one or a list of Data Class instances to be saved to the Database. It is invoked using the Script Functions BDSSaveDataClassInstance and BDSSaveDataClassInstances.
- For each Data Class instance received as an input, the Save operation inserts it if it was never saved before (persistence ID not available), otherwise (persistence ID available) it updates it.
- If the Save operation is invoked with cascade enabled (
cascade = true
), it also processes all the Data Class instances referenced by properties withcascade mode = save
ordelete
.
- All involved Data Classes must be synchronized with the Database.
- Given the set of all provided Data Class instances and (if `cascade = true`) all the ones included in their cascaded properties, there cannot be two different instances with the same persistence ID but different types or property values.
-
If a Data Class instance that already exists in the Database is found to have been removed from a property with `cascade mode = delete`, it is deleted from the Database.
-
The Save operation returns back the saved Data Class instances, updated as follows:
-
Instances that were inserted are enriched with the persistence ID.
-
Deleted instances in collections and in the list of instances provided as inputs in the operation are replaced by null values.
Note that the null values can be removed using the cleanupCollectionMode parameter of the Script Functions BDSSaveDataClassInstance and BDSSaveDataClassInstances
-
Data Placeholders are kept in the updated instances (although referenced instances are temporarily loaded during the Save operation to propagate cascading, if enabled). However, if the value of the property that contains a Data Placeholder is found to be null (e.g. because the instance referenced by the Data Placeholder was deleted), null is returned instead of the Data Placeholder as the property content.
Example
1.Given the FNZ Studio Data Model and cascade settings: Person DataClass
- String country
- Account account (cascade mode = Save)
- Named Address addresses (cascade mode = Delete)
- Indexed Person friends (cascade mode = Load)
Address DataClass
- String city
Account DataClass
- String type
2.In the following situation:
- the $person instance of the Data Class Person contains a persistence ID (has already been saved in the Database)
- the value 'Switzerland' is assigned to the country property of the $person instance
- $person.country := 'Switzerland';
- the $person instance contains an Account $account in the property account
- Account $account := $person.account;
- the value 'individual' is assigned to the type property of the $account instance
- $account.type := 'individual';
- the entry with key 'email' of the addresses property of the $person instance is set to null. It previously contained the Address $email (with persistence ID).
- $person.addresses['email'] := null;
- the $residence instance of the Data Class Address (without persistence ID) is assigned to the key 'residence' of the addresses property of the $person instance
- $person.addresses['residence'] := $residence;
- the Person instance $friend (with a persistence ID) is added to the friends property of the $person instance
- $person.friends.addElement($friend)
- the value 'US' is assigned to the country property of the $friend instance
- $friend.country := 'US';
3.If the Save operation is invoked on the $person instance with cascade enabled, the following results are obtained:
- the value of the country property of the $person instance is updated in the corresponding main table.
- the value of the type property of the $account instance is updated in the corresponding main table (since the account property of the Person Data Class has
cascade mode = Save
). - the $email instance is deleted from the Database (since it was removed from a property with
cascade mode = Delete
). - the bridge table corresponding to the addresses property of the Person Data Class is updated accordingly with a null value for the key 'email'.
- the $residence instance is inserted in the Database (since it was added to a property with
cascade mode = Delete
and it did not have a persistence ID) and it is enriched with the newly assigned persistence ID. - the bridge table corresponding to the addresses property of the Person Data Class is updated accordingly with a reference to the persistence ID of the $residence instance for the key 'residence'.
- the bridge table corresponding to the friends property of the Person Data Class is updated accordingly with a reference to the persistence ID of the $friend instance.
- the change to the country property of the $friend instance is not applied to the Database (since it is contained in a property with
cascade mode = Load
).
Save Operation: Special Cases
- If a Data Class instance is requested to be both deleted and updated in the same Save operation, then, it is deleted. This can happen, for example, if the same Data Class instance is found in two properties of a Data Class to be saved: in the first case, it is found in a property with
cascade mode = Save
, while in the second case it has been removed from a property withcascade mode = Delete
. - If a given Data Class instance to be saved is found to be associated with a persistence ID, but no corresponding entry is found in the Database for that persistence ID, the Data Class instance is then inserted in the Database with a new persistence ID. This can happen, for example, in case of concurrent data modification by multiple users or processes.
- If a Data Placeholder is found in a Data Class instance to be inserted in the Database, the Save operation fails. This applies also to the situation above where the instance was originally meant to be updated but its persistence ID was not found in the Database.
Load Operation
The Load operation works in the following way:
- The Load operation receives, as an input, one or a list of persistence IDs to be loaded from the Database and their type (Data Class ID). It is invoked using the Script Functions BDSLoadDataClassInstance and BDSLoadDataClassInstances.
- For each persistence ID received as an input, it loads all SV (Simple Value) and CSV (Collection of Simple Values) properties to corresponding Data Class instances and returns them as a result.
- If the Load operation is invoked with
cascade
set totrue
, it loads also all Data Class instances referenced by properties with cascade mode Load, Save or Delete of the Data Class instances provided as an input, recursively. - If a CV or CCV property is not cascaded (either because
cascade
is set tofalse
for the operation or because thecascade mode
for the property is set toNone
), a Data Placeholder is returned instead of the content of the property. - However, if the value of the property is found to be null in the Database, null is returned instead of the Data Placeholder as the property content.
- When cascading the Load operation to targets of CV and CCV properties, it may happen that the persistence ID found in the Database to represent the target Data Class instance is not found in the corresponding main table.
- If this happens, the Load operation does not fail, and it sets the CV property or the element of the CCV property to null.
Note that the null values can be removed using the parameter cleanupCollectionMode of the Script Functions BDSLoadDataClassInstance and BDSLoadDataClassInstances.
Delete Operation
The Delete operation works in the following way:
- The Delete operation receives, as an input, one or a list of persistence IDs to be deleted from the Database and their type (Data Class ID). It is invoked using the Script Functions BDSDeleteDataClassInstance and BDSDeleteDataClassInstances.
- For each persistence ID received as an input, it deletes all the corresponding data from the Database (main table, collection and bridge tables).
- If the Delete operation is invoked with
cascade
set totrue
, it also deletes all Data Class instances referenced by those properties of the Data Class instances provided as an input which havecascade mode
set to Delete, recursively.
Note that the Delete operation does not remove entries from bridge tables where the Data Class instance being deleted is the 'target' of the relationship, but only the ones where it is the 'source'. Those remaining bridge table entries are ignored by subsequent Load operations (see the Load Operation section).
Operation Example
You can download the .AWDEPLOYMENT file with the example here BDSCascadeExample. Sample Use Cases
- Create a new client with a new account (the client is the holder)
- Create a new client and connect it to an existing account
- Add documents to an existing account
- Remove a client and all its participation in any accounts
- Modify the address of a client
- Remove an address from a client
- Change the account holder
- Create an account search page
Sample scripts
// 1. Create a new client with a new account (the client is the holder)
BDSCascadeExample:Client $c := new BDSCascadeExample:Client;
$c.name := 'Mark';
// inserts client, nothing to cascade
Long $cId := BDSSaveDataClassInstance($c, false);
BDSCascadeExample:Document $doc1 := new BDSCascadeExample:Document;
$doc1.title := 'ID Card';
BDSCascadeExample:Account $a := new BDSCascadeExample:Account;
$a.documents := [$doc1]:BDSCascadeExample:Document;
$a.holder := $c;
$a.participants := [$c]:BDSCascadeExample:Client;
$c.accounts := [$a]:BDSCascadeExample:Account;
// inserts account, document and account-document, account-client and client-account relationships(since holder is cascade Save)
Long $accId := BDSSaveDataClassInstance($a, true);
//
// 2. create a new client and connect it to an existing account
// $accId comes, for example, from an account search page
// loads account, documents, holder, participants and their accounts, addresses, cities
// needs to be cascaded to get the existing participants list to append to
BDSCascadeExample:Account $a1 := BDSLoadDataClassInstance(BDSCascadeExample:Account, $accId, true);
BDSCascadeExample:Client $c1 := new BDSCascadeExample:Client;
$c1.name := 'Jon';
$c1.accounts := [$a1]:BDSCascadeExample:Account;
// inserts client, relationship with account, nothing to cascade
Long $cId1 := BDSSaveDataClassInstance($c1, false);
$a1.participants.addElement($c1);
// creates account-client relationship, nothing to cascade
BDSSaveDataClassInstance($a1, false);
//
// 3. add documents to an existing account
// $accId comes, for example, from an account search page
// loads account, documents, holder, participants and their accounts, addresses, cities
// needs to be cascaded to get the existing documents list to append to
BDSCascadeExample:Account $a2 := BDSLoadDataClassInstance(BDSCascadeExample:Account, $accId, true);
BDSCascadeExample:Document $doc2 := new BDSCascadeExample:Document;
$doc2.title := 'accountStatements';
$a2.documents.addElement($doc2);
// inserts document (since documents is cascade Delete) and account-document relationship
BDSSaveDataClassInstance($a2, true);
//
// 4. remove a client and participation in all accounts
// $cId comes, for example, from a client search page
// deletes client, all its addresses, relationships with addresses, relationship with branch, relationships with accounts
BDSDeleteDataClassInstance(BDSCascadeExample:Client, $cId, true);
// Note:
// this leaves the relationships from accounts to this client in the DB
// these will be ignored when reading the account (collections may contain a null element)
//
// 5. modify the address of a client
// $cId comes, for example, from a client search page
// loads client, addresses, cities, accounts, documents, holder and participants and their accounts, addresses, cities
// needs to be cascaded to get existing addresses
BDSCascadeExample:Client $c2 := BDSLoadDataClassInstance(BDSCascadeExample:Client, $cId1, false);
BDSCascadeExample:Address $addr := new BDSCascadeExample:Address;
$addr.street := '123 Washington St.';
$c2.addresses := [$addr]:BDSCascadeExample:Address;
// inserts address and client-address relationship
BDSSaveDataClassInstance($c2, true);
// loads client, addresses, cities, accounts, documents, holder and participants and their accounts, addresses, cities
// needs to be cascaded to get existing addresses
$c2 := BDSLoadDataClassInstance(BDSCascadeExample:Client, $cId1, true);
// the address to be modified is selected from the UI
BDSCascadeExample:Address $ad1 := $c2.addresses[1];
$ad1.street := '45 Regents St.';
// saves the address, nothing to cascade
BDSSaveDataClassInstance($ad1, false);
//
// 6. remove an address from a client
// $cId comes for example from a client search page
// loads client, addresses, cities, accounts, documents, holder and participants and their accounts, addresses, cities
// needs to be cascaded to get existing addresses
BDSCascadeExample:Client $c3 := BDSLoadDataClassInstance(BDSCascadeExample:Client, $cId1, true);
// the address to be removed is selected from the UI
BDSCascadeExample:Address $ad2 := $c3.addresses[1];
$c3.addresses.removeElement($ad2);
// removes the address and the client-address relationship
BDSSaveDataClassInstance($c3, true);
//
// 7. change account holder
// $accId comes, for example, from an account search page
// loads account, documents, holder, participants and their accounts, addresses, cities
// needs to be cascaded to get existing participants
BDSCascadeExample:Account $a3 := BDSLoadDataClassInstance(BDSCascadeExample:Account, $accId, true);
// $cId comes for example from a client search page
// loads client, addresses, cities, accounts, documents, holder and participants and their accounts, addresses, cities
// needs to be cascaded to get existing accounts
BDSCascadeExample:Client $c4 := BDSLoadDataClassInstance(BDSCascadeExample:Client, $cId1, true);
$a3.holder := $c4;
$a3.participants.addElement($c4);
$c4.accounts.addElement($a3);
// updates accounts and relationships. Removes relationship from account to old holder, but non the old holder client (since holder is cascade Save and not Delete). Adds new relationship client-account since holder is cascade Save
BDSSaveDataClassInstance($a3, true);
//
// 8. Create account search page
// loads all accounts, but not the related documents and clients (not needed for the search results page) --> cascade flag is false
Indexed BDSCascadeExample:Account $accounts := BDSFindDataClassInstances(BDSCascadeExample:Account, 'select * from ' & BDSGetMainTableName('BDSCascadeExample:Account'), null, false);
// then in order to fully load the selected Account
Long $accId1 := BDSGetPersistenceId($accounts[1]);
// loads client, addresses, cities, accounts, documents, holder and participants and their accounts, addresses, cities
// cascaded to get all info for the account page
BDSLoadDataClassInstance(BDSCascadeExample:Account, $accId1, true);