Using instance filters
In order to reduce the instances displayed for a collection, property handles provide two ways of filtering instances in a collection. One way is by setting an OSI expression as filter condition for the property handle. The other possibility is to set an instance or key to hidden when reading an instance, i.e. using the doAfterRead() handler for filtering instances from the collection.
When a filter has been set, the property handle selects only those instances that return true (-> Value::isTrue()) for the filter expression. Sequential retrievals as nextKey(), next(), previous() or position() automatically search for the next valid instance that fulfills the filter condition. locateKey() will return an error (not found) when the instance does not fulfill the selection criteria. previousKey() and nextKey() will skip invalid instances as well.
The get() function, which is requesting a specific instance by index or key, throws an exception when the requested instance does not fulfill the filter condition. You may call tryGet() in order to check, whether the instance is valid for the filter condition.
When setting a filter for an update or write property handle, updating an instance may lead to an invalid instance (i.e. the instance is not fulfilling the defined condition anymore). In this case, the instance is unselected after storing the updated data.
Setting a filter condition slows down key operations as e.g. nextKey(), since the instance must be read in order to check the condition. When a condition is based on key component attributes, only, performance can be improved by calling keyFilter() instead. Since instance reading is much more expensive than key reading, filtering by key value is much more efficient than filtering by instance.
The filter conditions (OSI expressions) can be set in the filter() property. The set and get functions of the property allow changing and reading the filter condition. You may expand a filter condition by calling expandFilter(). hasFilter() can be called in order to check, whether a filter condition had been defined or not.
// OSI: access path with selection condition
set<Person> adults = Person.Where{age > 18};
while ( adults.next )
displayPerson(adults);
// C++. C# ...
Property adults(database,"Person",Read);
adults.SetFilter("age > 18");
while ( adults.next() )
displayPerson(adults);
The count() property does not reflect filter conditions, i.e. it returns always the total number of instances in the collection, independent whether a filter condition has been set for the collection or not.The number of instances according to the filter set in the property handle can be retrieved using the relativeCount() function.
if ( adults.relativeCount() > 0 )
Message("Person has grown up children");
Referring to an instance by position or key may fail, since the selected instance at a given position in the collection may not fulfill the filter condition. In this case, the function call fails. In order to access instances by position although a filter has been set, getRelative() might be called.
// C++. C# ...
Property adults(database,"Person",PI_Read);
int indx0 = 0;
adults.SetFilter("age > 18");
while ( adults.getRelative(indx0++) )
displayPerson(adults);
Even though, filter expressions can be as complex as possible, filtering may depend on other variables, e.g. run-time variables. In this case, filtering can be done easier by using context class functions. In order to support filtering based on key data, the doBeforeRead() handler can be used. Instance filtering should be done in the doAfterRead() handler.
Context selection is based on the doBeforeRead() handler that handles the DBP_Read event. This event is generated always, when an instance is going to be read, i.e. after the instance has been located in the collection. The event is also generated when using key access methods as nextKey() or locateKey().
For ordered collections (when an access key is selected for the collection) the key is available as well as the LOID of the instance. The doBeforeRead() handler can be overloaded in order to suppress instances, that do not follow certain conditions. In order to skip (deny) the current instance, the function has to return true. One may, however, also mark the instance as hidden (visible(false)). Since instance information is not guaranteed, the check can be made based on data of the key instance or the identity, only.
// accept persons with names lower than 'P'
bool sPerson:: doBeforeRead() { // TypeContext class
Property *ph = property();
return( ( ph.getKey("name").string() <= 'P' ? false : true); //true for ignore
}
bool sPerson:: doBeforeRead() { // TypeContext class
Property *ph = property();
visible(ph.getKey("name").string() <= 'P'); //false for hide
}
Since the event is generated when providing key values and before reading an instance, in some cases the event might be generated twice (when locating the key first and then reading the instance). This happens, e.g. when combining key access and expression selection (->see Key check).
In order to check instance filter conditions, the doAfterRead() handler must be used, which handles the DBO_Read event. This event is generated after reading or rereading instances. When the handler has been called, instance data can be accessed. Since the doAfterRead() handler is an "after" event, which cannot influence the process logic, it must hide the instance explicitly.
Hiding the instance will suppress the selection of theinstance when trying to read it with get(). When using position(), next() or previous(), hidden instances are skipped.
The 'hidden' state is automatically reset when the selection in the property handle changes the selection. It is, however, also possible to reset the 'hidden' state using the context function showInstance(). The 'hidden' state is inherited to derived structure instances.
// accept adult persons, only (age >= 18)
bool sPerson:: doBeforeRead() {
if ( value("age").toInteger() < 18 )
visible(false);}
return ( false );
}
For hiding instances while accessing keys (e.g. with nextKey() or locateKey()), the?hidden? state must be set in the doBeforeRead() handler, since this is the only event that is generated when reading keys.
Sometimes, a filter condition is based on properties, which are all key components. Unfortunately, key functions as nextKey() or firstKey() will normally read the instance before checking the condition, i.e. key access will not be as efficient as without selection.
When reading keys from the database and a filter is set, the instance is read in order to check whether the key refers to an accepted instance or not.
In order to provide fast selection for key filter, key check can be enabled, e.g. after setting the filter condition for the property handle by calling the keyCheck() property handle function. When key check is enabled for the property handle,key functions will check the selection condition based on the key selected for the property handle, only. When the expression refers to non-key properties, the initialized instance values are used for evaluation.
// OSI: access path with selection condition
set<Person> lowCaseName = Person.OrderBy("sk_Name").Where{name >= 'a'};
lowCaseName.keyCheck(true);
while ( lowCaseName.next ) // filter by key access
displayPerson(lowCaseName);
// C++. C# ...
Property lowCaseName(database,"Person",Read);
lowCaseName.keyCheck(true);
lowCaseName.setAccessKey("sk_Name");
lowCaseName.filter("name >= 'a'");
while ( lowCaseName.next() ) // filter by key access
displayPerson(lowCaseName);