Skip to main content

Aggreagate persistence: Persist data

Implement Commands

Factory commands

The main purpose of a factory command is to create a new instance of a root entity with initial values and then persist those changes to the database. Each factory command provides input data depending on what has been modelled in the Solution Designer before. As a return value, the factory command will automatically return the instance id of the created instance.

Here you can see an example implementation of a factory command, creating a new instance of root entity Order:

  /**
* Factory command execution
*/
public async execute(): Promise<void> {
// Read input properties
const { inputProperty1, inputProperty2 } = this.input;

// create a new instance of the Root entity
this.instance = this.factory.entity.Order();

// fill properties of the instance using the values from the input
this.instance.property1 = inputProperty1;
this.instance.property2 = inputProperty2;

// Set the instance property to an initial value
this.instance.property3 = 'value3';

// Save changes to the database
await this.instance.persist();
}

Instance commands

Instance commands usually hold logic to modify the state of a root entity instance and persist the updated state in the database. The instance that can be modified as well as the input of the command is provided automatically.

Here you can see an example implementation of an instance command, modifying an existing instance of root entity Order and throwing a business error if some special condition is met:

  /**
* Instance command execution
*/
public async execute(): Promise<void> {
// get value from input
const { newPropertyValue } = this.input;

// Check current state of instance
if (this.instance.property1 === 'SomeSpecialState'){
// throw business error
throw this.factory.error.nsacrnm.MyBusinessError();
} else {
// update property with new value
this.instance.property1 = newPropertyValue;
}

// Save changes to the database
await this.instance.persist();
}

The deletion of a root entity should be also handled within an instance command:


/**
* Instance command deleting execution
*/
public async execute(): Promise<void> {
// Delete instance
await this.instance.delete();
}
warning

When deleting an instance, no persist call is needed to delete the instance from the database. Using delete() is sufficient.

ℹ️note

If you are using the aggregate persistence functionality, please ensure that your code is compliant with the recommended restrictions of the used database. Consult MongoDB documentation for further information. For example: Because of the limitations of database operations and drivers, MongoDB does not recommend the use of timestamps, that are not within the year range of 0 - 9999, see Date and DateTime documentation.

Trigger Commands

The generated SDK provides functions to easily trigger commands from other places in the service.

Input entity

When triggering a command, it might be necessary to provide an input entity. The Entity factory can be used to create new input entities, which could be set as input parameter for the command.

Trigger factory commands

Factory commands can be triggered through the provided Repository, which holds an object for each root entity, grouped by domain namespace.

The following code shows how to trigger a factory command, for example from a service implementation.

// Initialize the input 
const input = this.factory.entity.nsacrnm.FactoryCommandIdentifier_Input();
input.property1 = "value";

// Access the repository of the specific root entity and choose the factory command
const rootEntityInstance = await this.repo.nsacrnm.RootEntityIdentifier.FactoryCommandIdentifier(input)

// Access the id of the root entity instance
rootEntityInstance._id;

// Access the properties of the root entity instance
rootEntityInstance.property1;

Trigger instance commands

Instance commands can be executed on instances of aggregates, which were usually loaded before from the database by using the Repository or returned from a factory command.

The following code shows how to trigger an instance command, for example from a service implementation.

// Initialize the input 
// Access the repository of the specific root entity and choose the the factory command or search for a specific instance of the root entity
// Here we call a factory command
const rootEntityInstance = await this.repo.nsacrnm.RootEntityIdentifier.FactoryCommandIdentifier(input)

// Initialize the input
const input = this.factory.entity.nsacrnm.InstanceCommandIdentifier_Input();
input.property1 = "value";

// From the returned root entity instance, it is possible to access the instance commands that are related to this instance and execute them accordingly
await rootEntityInstance.InstanceCommandIdentifier(input);

Reference factory

If aggregate persistence support is enabled, the SDK provides a Factory for root entity references. The factory offers methods to create new references to root entities, grouped by the namespaces in which the entities are defined.

If a root entity referenced is modelled as a property in another entity the return value of the factory call can be directly used there. The response also allows to access the id of the root entity and load the root entity.

// Create an entity reference for root entity using reference factory
const ref = this.factory.reference.nsacrnm.RootEntityIdentifier(someEntityId);

// use root entity reference as property in another entity
entity1.someRootEntityReference = ref;

// read the id of the root entity
const id = ref.refId;

// load referenced entity from database
const rootEntityInstance = await ref.load();

Implement External Entities

The external entity script contains 3 parts:

Constructor

The constructor is used to construct a local instance of the external entity and initialize the associated properties with certain pre-specified values that are provided as input properties.

//**
* constructor method of external entity reference
*/
public create(): void {
const log = this.util.log;
log.debug('Constructor.execute()');

// Initialize the value of the associated properties to the input value store in the construc-tor parameter and to a predefined value
this.instance.associatedProperty1 = this.input.constructorProperty1;
this.instance.associatedProperty1 = value1;
}

Loader

The loader is used to load the external instance. This is normally done by calling a REST service of the integration namespace.

When calling a service (REST or normal), it is important to check whether the output exists, and it is not void. Due to type safety, if the check does not occur, then it is not possible to access the properties of the returned object. This is to prevent accessing properties that may not exist. This check is necessary for calling functions that are built to return two different types such as services that return either void or their output entity.

/**
* load method of external entity reference - loads external entity from external system
*/
public async load(): Promise<void> {
const log = this.util.log;
log.debug('Load.execute()');

// Initialize the input of the REST integration service
const input = this.factory.entity.cust. ServiceIdentifier_Input();

// Initialize the input properties to their values
input.property1 = “value1”; // or this.instance.(…)

// Call the REST service and pass as input the the input entity created above
const serviceOutput = await this.services.intnsacrnm.ServiceIdentifier (input);

// Check if the output of the service is not void
if(serviceOutput) {

// Initialize the output of the loader of the external entity
this.output = this.factory.nsacrnm.ExternalEntityIdentifier_Output()

// Set the values of the output properties of the output
this.output.property1 = serviceOutput.property1;
this.output.property2 = serviceOutput.property2;
}
}

Validator

The validator gives information regarding the existence of a specific instance of an external entity. It usually returns true if the external instance exists and false if it does not exist. The validator can also be used for updating the local instance of an external entity if the update flag is set to true.

💡tip

In a validator, it is recommended to try to load the instance. If the instance is returned successfully, then return true. A validator must always return a boolean.

/**
* validate method of external entity reference - checks if external entity still exists in external system
* @param update indicates whether local properties should be updated after validating entity on external system
* @returns boolean indicating whether external entity still exists in external system
*/
public async validate(update: boolean): Promise<boolean> {
const log = this.util.log;
log.debug('Validate.execute()');

// trigger load function of external entity in order to validate whether the entity still exists
const existence = await this.instance.load();

// If it exists return true. Otherwise, return false.
if(existence) {
return true;
} else {
return false;
}
}

External entity factory

To make use of the external entities, the SDK provides a Factory for them. The factory offers methods to create new external entity objects, grouped by the namespaces in which the entities are defined. When using those, the constructor method of the external entity is called, which returns the local instance of the external entity from which it is possible to access the associated properties as well as call the load and validate methods.

Please see the example below:

// Initialize the input to the external entity constructor
const input = this.factory.entity.ExternalEntityIdentifier_Input();

// Create external entity reference
const extEntity = await this.factory.external.nsacrnm.ExternalEntityIdentifier(input);

// Access the associated properties
extEntity.assProp1;

// Load external entity - no input needed
const extEntityInstance = await extEntity.load();

// Now, one can access the properties and their values (read-only) of the specific instance
extEntityInstance.property1;
extEntityInstance.property2;

// Validate the existence specific instance - this will return true or false
const valResult = await extEntity.validate();
ℹ️note

To use external entities in properties within other entities, please use the external entity factory to create objects and assign.

Repository

When aggregate persistence support is enabled, the SDK provides access to the database through a repository. Using this, the connection to the underlying database is automatically established. As Aggregates are considered as a persistable unit, one repository per root entity is created which allows to read items from the database. Each of the returned instances offer a persist and delete method which can be used for saving and deleting data in the database.

Access the repository

The repository is provided within the scope of command, service or agent implementations. The root entities are grouped within namespaces.

  // access repository
this.repo.ns.RootEntityIdentifier;

Find

The find method offered by the repository allows to load one or many aggregates from the database. It takes a RepositoryFindConfiguration as first input parameter and a filter expression as a second parameter. Using the parameters, the returned result can be influenced.

Example:

// load all instances of a certain root entity type from the database
const rootEntityInstances = await this.repo.ns.RootEntityIdentifier.find();

// loop over instances
for (const inst of rootEntityInstances) {
// do something
// ...
}

Example using find configuration and filter expression:

// create a special find configuration
const findConfiguration = {
// include child instances - default is false
includeSubentities: true,
// restrict the received result to a maximum amount - format is "<offset>,<amount>"
limit: "0,20",
// overwrites the sort order of the received instances - format is "<localIdentifier>,<ASC|DESC>"
sortBy: "customerID,DESC"
};

// create a filter expression that only returns items with a special name
const filterExpression = `name == "${name}"`;

// load instances from database
const rootEntityInstances = await this.repo.ns.RootEntityIdentifier.find(findConfiguration, filterExpression);

A more detailed description of filter expressions is available at filter expressions

Find by id

The findById method offered by the repository allows to load one exact aggregate from the database by using the provided id. The id is an auto generated uuid which is e.g. used when a root entity is referenced in another entity. In case that an instance with the provided id cannot be found in the database, an AggregateNotFoundError will be thrown.

Example:

// load instance from database
const rootEntityInstance = await this.repo.ns.RootEntityIdentifier.findById(`EntityId`);

// save instance in database
await rootEntityInstance.persist();

// delete instance from database
await rootEntityInstance.delete();

Factory Commands

The repository also provides access to the factory commands of a root entity. To find out more about this, please check Trigger Commands.

Filter expressions

Introduction

Filter expressions are used to find data objects in the database. Their basic principle is to define a comparison. A comparison consists of a path, an operator and a value, where

  • path: points to a property of the entity. Traversing of the object tree is supported and is indicated by a single dot "." (e.g. customer.name).
  • operator: indicates the operation that is applied to the comparison.
  • value: specifies the value to which the comparison is applied. It is highly dependent on the type of the property and operation.
ℹ️note

A comparison is always surrounded by single back ticks. They can be grouped by parenthesis and joined using logical operators.

Defining the path

In general, the path to an object is always relative to the Entity which is going to be queried.

Traversing within the object tree can be done via concatenating the names of the properties with a single dot ".". Despite the modelled properties, there are always two artificial properties available:

  • _type: The type describes the type of the entity. Its value is given as fully qualified entity name, e.g. core: MyEntity.
  • _id: The id describes the internal id of the entity.

For the property type Currency there are also two suffixes available (amount, currency), which allow the specification whether the amount value or the currency code of the property should be compared. The suffix is concatenated with #. The suffixes are constant strings.

For the property type Localized Text, there is also a suffix available. This value is concatenated by using # followed by a string, that points out to the locale value, that should be compared. Example: #en

Operators

Operators can be expressed through their symbols as well as through their alias.

For combining different filter criteria, the logical operators AND and OR can be used.

NameSymbolAliasDescription
EQ===eq=Checks for equality between the value of the given property and the specified value.
NEQ!==neq=Checks for inequality between the value of the given property and the specified value.
TEXT_STARTS_WITH^*=tsw=Checks if the value of the given property starts with the specified value. The check is not done case-sensitive.
TEXT_ENDS_WITH*$=tew=Checks if the value of the given property ends with the specified value. The check is not done case-sensitive.
TEXT_CONTAINS**=tco=Checks if the value of the given property contains the specified value. The check is not done case-sensitive.
LT<=lt=Checks if the value of the given property is lower than the specified value.
LTE<==lte=Checks if the value of the given property is lower or equal than the specified value.
GTE> ==gte=Checks if the value of the given property is greater or equal than the specified value.
GT> =gt=Checks if the value of the given property is greater than the specified value.
SUBCLASS=*=sc=Checks if the value of the given property is a subclass of the specified value.
IN=in=Checks if the value of the given property is part of the specified array of strings. This check is case-sensitive.
CONTAINS=co=Checks if there exists one object in the list of objects at the given property, which fits to the given filter expression.
NEARBY=nb=Checks if the value of the given property is located in the specified location. The location is expressed by [<lat>,<lon>;<range>]
ℹ️note

Not all operators are available for all types of property.

Supported operators per property type

According to the type of the property, that is denoted in the path of a comparison node, there are different operators, that are supported.

Property TypeAllowed OperatorsNotes
Text, Text/E-mail,Text/URLEQ, NEQ, TEXT_STARTS_WITH, TEXT_ENDS_WITH, TEXT_CONTAINS, INThere is no notion of a wildcard sign available for any of the values
Integer, Decimal, LongEQ, NEQ, LT, LTE, GTE, GT
BooleanEQ, NEQ
CurrencyEQ, NEQCurrency cannot be searched without specifying a suffix (#amount resp. #currency)
Selection ElementEQ, NEQ, INThe key of the selection element must be given as comparison value
Date, Time, TimestampEQ, NEQ, LT, LTE, GTE, GTThe value is passed as a string in format of ISO 8601 (YYYY-MM-DDThh:mm:ss.sssZ). The precision for the search is determined by the precision of the given value
Geo PointNB
Localized TextEQ, NEQ, TEXT_STARTS_WITH, TEXT_ENDS_WITH, TEXT_CONTAINSThe value can only be searched, if the locale of the property is specified by suffixing the property path with #locale, where the locale is given in java-Locale format, e.g. #en or #en_GB
Reference (single)No operators are available
Local Entity (list), External Reference (list), Reference (list)CONTAINSList values can be queried by specifying a CONTAINS operator. The given value itself is again a comparison. If there exists at least one object in the list that is matching the filter expression, it evaluates to true
_type (artificial property)EQ, NEQ, IN, SUBCLASSSearches for the entity type
_id (artificial property)EQ, NEQ, INSearches for the (internal) id of the entity

Defining values

To compare a given property's value via some operator, a value must be specified. According to the operator and the type of the property, different types of supplying values are allowed.

  • String-Value: A simple string, that may contain arbitrary characters. The value needs to be quoted by double quotes ". For passing the character " to the value, this character needs to be escaped by putting a backslash "" in front of the character. For passing the character \ to the value, this character needs to be written as \. Example: " my simple arbitrary string value, that even contains a " special escaped char."
  • String-Array-Value: List of strings in array-notation separated by a comma ",". Example: ["a", "b", "c"]
  • Number-Value: A number that may be prefixed by a signum and also contain a decimal value, which is denoted by a single dot. Also, the scientific representation of numbers via e is allowed. Example: -23.14
  • Boolean-Value: A boolean value that can be true or false
  • Null-Value: Filtering for properties with no value is indicated by the (not quoted) keyword null
  • Location-Radius-Value: The value is a combination of the decimals: latitude, longitude and range. It will be accepted in the following format: [<lat>,<lon>;<range>d]. If the range is negative, this means that it is considered as a minimum distance. All three elements must be given. The range is the radius in meter around the given geoPoint in which the result must be
    • Example: [49.011370, 12.095336; 5000] // all elements within a range of 5000 m of the point
    • Example: [700.43, -9999; 3] // invalid value since longitude cannot be negative
    • Example: [49.011370, 12.095336; -300] // minimum distance of 300 m of the given point

Overview of allowed operators, property types and values

Property TypeOperatorValue TypeExample
Text, Text/E-mail, Text/URLEQString-ValuemyStringProperty=="abc"
Text, Text/E-mail, Text/URLEQNull-ValuemyStringProperty == null
Text, Text/E-mail, Text/URLNEQString-Value, Null-ValuemyStringProperty != "abc"
Text, Text/E-mail, Text/URLINString-Array-ValuemyStringProperty =in= ["abc", "def"]
Text, Text/E-mail, Text/URLTEXT_STARTS_WITHString-ValuemyStringProperty ^* "abc"
Text, Text/E-mail, Text/URLTEXT_ENDS_WITHString-ValuemyStringProperty =tew= "abc"
Text, Text/E-mail, Text/URLTEXT_ENDS_WITHString-ValuemyStringProperty ** "abc"
Integer, Decimal, LongEQNumber-Value, Null-ValuemyIntegerProperty == 3
Integer, Decimal, LongNEQNumber-Value, Null-ValuemyDecimalProperty != 3.12
Integer, Decimal, LongLTNumber-ValuemyDecimalProperty < 4.0e+5
Integer, Decimal, LongLTENumber-ValuemyDecimalProperty <= 4.0
Integer, Decimal, LongGTENumber-Value
myIntegerProperty >= 4
Integer, Decimal, LongGTNumber-ValuemyIntegerProperty > 4
Currency (amount)EQNumber-ValuemyCurrencyProperty#amount == 12.22
Currency (amount)NEQNumber-ValuesmyCurrencyProperty#amount != 12.23
Currency (amount)LTNumber-ValuemyCurrencyProperty#amount < 4.0e+5
Currency (amount)LTENumber-ValuemyCurrencyProperty#amount <= 4.0
Currency (amount)GTENumber-ValuemyCurrencyProperty#amount >= 4
Currency (amount)GTNumber-ValuemyCurrencyProperty#amount > 4
Currency (currency)EQNumber-ValuemyCurrencyProperty#currency == EUR"
Currency (currency)NEQNumber-ValuemyCurrencyProperty#currency != "USD"
BooleanEQBoolean-Value, Null-ValuemyBooleanProperty==true
BooleanNEQBoolean-Value, Null-ValuemyBooleanProperty =neq= false
Selection elementEQString-Value, Null-ValuemySelectionElementProperty == "keyOfFirstElement"
Selection elementNEQString-Value, Null-ValuemySelectionElementProperty == "keyOfFirstElement"
Selection elementINString-Array-ValuemySelectionElementProperty =in= ["keyOfFirstElement", "keyOfAnotherElement"]
Date, Time, TimestampEQString-Value (formatted as date), Null-ValuemyDateProperty == "2020-03-28"
Date, Time, TimestampNEQString-Value (formatted as date), Null-ValuemyTimestampProperty != "2020-03-28T13:24:11"
Date, Time, TimestampLTString-Value (formatted as date)myDateProperty < "2020-03-28"
Date, Time, TimestampLTEString-Value (formatted as date)myDateProperty <= "2020-03-28"
Date, Time, TimestampGTEString-Value (formatted as date)myDateProperty >= "2020-03-28"
Date, Time, TimestampGTString-Value (formatted as date)myDateProperty > "2020-03-28"
Geo PointNBLocation-Radius-ValuemyGeoPointProperty == [49.011370,12.095336;5000]
Localized TextEQString-ValuemyLocalizedProperty#en == "abc"
Localized TextNEQString-ValuemyLocalizedProperty#en_US != "abc"
Localized TextTEXT_STARTS_WITHString-ValuemyLocalizedProperty#ca_FR =tsw= "abc"
Localized TextTEXT_ENDS_WITHString-ValuemyLocalizedProperty#de =tew= "abc"
Localized TextTEXT_ENDS_WITHString-ValuemyLocalizedProperty#en ** "abc"
Local Entity (single), External Reference (single)Only usable within object tree traversionmyLocalEntity.myStringProperty == "abc"
Local Entity (list)CONTAINSFilter-Expressions-ValuemyLocalEntityList =co= (myStringPropertyOfLocalEntity == "abc")
External Reference (list), Reference (list)CONTAINSFilter-Expressions-ValuemyExternalReferenceList =co= (myStringPropertyOfExternalEntity == "abc")
_type (artificial property)EQString-Value (specifying the fully qualified type of the entity)myLocalEntity._type == "core:MyLocalEntity"
_type (artificial property)NEQString-Value (specifying the fully qualified type of the entity)_type != "core:MyRootEntity"
_type (artificial property)INString-Array-Value (specifying the fully qualified type of the entity)myLocalEntity._type =in= ["core:MyLocalEntityA", "core:MyLocalEntityB"]
_type (artificial property)SUBCLASSString-Value (specifying the fully qualified type of the entity)type =sc= "core:MyAbstractEntity"
_id (artificial property)EQString-ValuemyLocalEntity._id == "5b87e25d-035d-40aa-9209-b0c04b004686"
_id (artificial property)NEQString-Value_id != "5b87e25d-035d-40aa-9209-b0c04b004686"
_id (artificial property)INString-Array-ValuemyLocalEntity._id =in= ["5b87e25d-035d-40aa-9209-b0c04b004686", "bd8b3a45-3582-40e3-b969-306698a5addf"]

Grammar

Please find below a formal (simplified) description of the grammar, that is used for filters.

ROOT: NODE | '(' NODE ')';

NODE: LOGICAL | COMPARISON;
NODE_IN_BRACKET: LOGICAL_IN_BRACKET | COMPARISON_IN_BRACKET;

COMPARISON: COMPARISON_IN_BRACKET | COMPARISON_PLAIN;
COMPARISON_IN_BRACKET: '(' COMPARISON_PLAIN ')';
COMPARISON_PLAIN: PATH OPERATOR VALUE;

LOGICAL: LOGICAL_IN_BRACKET | LOGICAL_PLAIN;
LOGICAL_IN_BRACKET: '(' LOGICAL_PLAIN ')';
LOGICAL_PLAIN: NODE_IN_BRACKET LOGICAL_OPERATOR NODE_IN_BRACKET;

LOGICAL_OPERATOR: 'AND' | 'OR';
OPERATOR: '==' | '=eq=' | '!=' | '=neq=' | '<' | '=lt=' | '<=' | '=lte=' | '>=' | '=gt=' | '>' | '=gte=' | '=*' | '=sc=' | '=in=' | '=nb=' |'=co=' ;

PATH: PROPERTY_NAME_SUFFIX | PROPERTY_NAME_ARTPROP| PROPERTY_NAME_PATH | PROPERTY_ART_PROP;
PROPERTY_NAME_SIMPLE: [a-z]+ (_[A-Za-z0-9])+;
PROPERTY_NAME_PATH: PROPERTY_NAME_SIMPLE ('.' + PROPERTY_NAME_SIMPLE)*;
PROPERTY_NAME_ARTPROP: PROPERTY_NAME_PATH '.' PROPERTY_ART_PROP;
PROPERTY_NAME_SUFFIX: PROPERTY_NAME_PATH '#' PROPERTY_SUFFIX;
PROPERTY_SUFFIX: 'amount' | 'currency' | LOCALE;
PROPERTY_ART_PROP: '_id' | '_type';
LOCALE: [a-z][a-z] ('_' [A-Z][A-Z])?;

VALUE: STRINGVALUE | NUMBERVALUE | BOOLEANVALUE | NULLVALUE | NODEVALUE | GEOVALUE | STRINGARRAYVALUE;

STRINGVALUE: STRING_TOKEN;
STRING_TOKEN: '"' STRING_PRIMITIVE '"';
STRING_PRIMITIVE: CHAR_PRIMITIVE*;
CHAR_PRIMITIVE: CHAR_ESCAPE | CHAR_NORMAL;
CHAR_ESCAPE: '\"' | '\\';
CHAR_NORMAL: [^\"][^\\];

STRINGARRAYVALUE: '[' STRINGARRAYVALUE_ELEMENTS ']'
STRINGARRAYVALUE_ELEMENTS: STRING_TOKEN (',' STRING_TOKEN)*

NUMBERVALUE: NUMBER_PRIMITIVE;
NUMBER_PRIMITIVE: ('-')? [0-9]+ ('.' [0-9]+)? (('e'|'E')['+'|'-'](0-9)+)?

BOOLEANVALUE: 'true' | 'false';
NULLVALUE: 'null';

NODEVALUE: NODE_IN_BRACKET
GEOVALUE: '[' + NUMBER_PRIMITIVE + ',' + NUMBER_PRIMITIVE + ';' + NUMBER_PRIMITIVE +']';
ℹ️note

Please note that despite the syntactical rules outlined in the grammar, there are also semantic rules checked in addition.

Example usage within repository

// Access the repository of the specific root entity and choose find
// Find takes an object argument and a filter argument. The filter argument is represented as a string.

const rootEntityInstance = this.repo.nsacrnm.RootEntityIdentifier.find(FindConfigurationObj, Filter);

//Example
const rootEntityInstance = this.repo.nsacrnm.RootEntityIdentifier.find(
{ includeSubentities: true,
limit: "2,20",
sortBy: "customerID,DESC" },
`name == "${name}"`
);

Example usage with the filter factory

The Filter Factory provides support to construct the filter string to be used while querying the Datastore Api.

There are 4 methods available in the filter factory:

comparison

This method has 2 signatures,

  • with 3 arguments for property type localized text where first argument is locale string. For example, to create the filter with locale string 'en_US'

    this.factory.filter.namespace.entity.myLocalizedProperty.comparison('en_US', '!=', 'abc'); // resulting string,  myLocalizedProperty#en_US != "abc"`
  • with 2 arguments for all other property type except localized text and entity list

    this.factory.filter.namespace.entity.myTextProperty.comparison('==', 'test'); // resulting string,  myTextProperty == "test"
    this.factory.filter.namespace.entity.myCurrencyProperty.amount.comparison('>=', '100'); // resulting string, myCurrencyProperty#amount >= "100"
_and

This method returns filter string by adding logical operator AND to given condition.

this.factory.filter._and(condition1, condition2)
_or

This method returns filter string by adding logical operator OR to given condition.

this.factory.filter._or(condition1, condition2)
contains

This method is only exposed for entity list type of properties such as, local entity list, reference list and external list.

this.factory.filter.namespace.entity.myEntityListProperty.contains.myTextProperty.comparison('=eq=', 'test');

The filter factory provides type safe guidance to build and use the filter strings to make queries to the database instance.

This example shows usage of the filter factory methods:

const condition1 = this.factory.filter.namespace.entity.textProperty1.comparison('=in=', ['text1', 'text2']);
const condition2 = this.factory.filter.namespace.entity.booleanProperty1.comparison('==', true);

const filterExpression = this.factory.filter._and(condition1, condition2);
// can use OR using similar syntax this.factory.filter._or(cond1, cond2)
// filterExpression string: "((textProperty1=in=['text1', 'text2']) AND (booleanProperty1==true))"

const filteredInstance = await this.factory.namespace.RootEntity1.find({ includeSubEntities: true }, filterExpression);
ℹ️note

this.factory.filter will be available in Services, Commands and Operations (since this.repo method is also available in them). this.factory.filter will only be available for Root Entities but not for Entity or External Entity since this.repo does not exist in these types of entities.

Complex examples

Modelled entities:

cc:CreditCard <<root entity>>
- cardText: text
- cardType: selection element [MASTER, VISA, AMEX]
- owner: external reference to cc:Customer
- transactions: list of local entites of type cc:Transaction

cc:Transaction <<abstract entity>>
- transactionName: localized text
- transactionTimestamp: timestamp

cc:DefaultTransaction extends cc:Transaction
- details: text

cc:GoldTransaction extends cc:Transaction
- rating: selection element

cc:Customer <<external entity>>
- custName: text
- custNumber: integer

Query for credit cards with cardType MASTER or VISA:

const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`(cardType=in=["MASTER", "VISA"])`
);

Query for credit cards that belong to owner with custNumber 167671:

const custNumber = 167671;
const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: false },
`owner.custNumber == "${custNumber}"`
);

Query for credit cards, which do not have a cardText or which type is not AMEX:

const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`((cardText == null) OR (cardType != "AMEX"))`
);

Query for credit cards, which do have a transaction within the last 10 days:

const deadline = new Date();
deadline.setUTCDate(deadline.getUTCDate() - 10);
const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`(transactions =co= (transactionTimestamp >= ${deadline.toISOString()}))`
);

Query for credit card with the internal id 5b87e25d-035d-40aa-9209-b0c04b004686:

const card = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`_id == "5b87e25d-035d-40aa-9209-b0c04b004686"`
);

Query for credit cards which have one transaction that contains "abc" (english) as transactionName and where the owner is "Black Cat":

const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`((owner.custName == "Black Cat") AND (transactions =co= (transactionName#en =tco= "abc")))`
);

Query for credit cards which have a transaction that is subclass of GoldTransaction:

const cards = await this.repo.cc.CreditCard.find(
{ includeSubEntities: true },
`(transactions =co= (_type =sc= "cc:GoldTransaction"))`
);

Filter factory examples

Basic Implementation:

const filter1 = this.factory.filter.cc.CreditCard.cardName.comparison('=co=', ['James']);
const filter2 = this.factory.filter.cc.CreditCard.cardActiveFlag.comparison('==', true);

_id &_type as property of Credit Card:

const filter3 = this.factory.filter.cc.CreditCard._id.comparison('==', '18fecac3-9044-4e8b-a767-f427c3901e55');
const filter4 = this.factory.filter.cc.CreditCard._type.comparison('==', 'cc_CreditCard');

For currency property type:

const filter5 = this.factory.filter.cc.CreditCard.currency1.amount.comparison('==', '100');
const filter6 = this.factory.filter.cc.CreditCard.currency1.currency.comparison('==', 'USD');

For localized texts, here description property is of type localized text:

const filter7 = this.factory.filter.cc.CreditCard.description.comparison('ca_FR', '==', 'Canada');

Address as local Entity(single) or reference (single) or external reference (single):

const filter8 = this.factory.filter.cc.CreditCard.address.pincode.comparison('==', '93051');
ℹ️note

Currently only one level of local entity with its properties mentioned above is supported

Transactions as local entity List or reference list or external reference list:

The method contains which represents =co= operator is only available for these type of properties

const filter9 = this.factory.filter.cc.CreditCard.transactions.contains.transactionTimestamp.comparison('>=', new Date());
// resulting filter string: (transactions =co= (transactions.transactionTimestamp >= new Date()))
Course Implement Domain logic