Saturday, March 7, 2009

LINQ in X++

LINQ (Language INtegrated Query) was introduced in C# 3.0 and was created for unified SQL-like inquiry interface to any data source: database, XML files, or object collections.

In C# it can be performed in SQL-like format or through extention methods:
collection.Where(....), collection.Select(....)

In X++ SQL for DB access is already implemented, but sometimes developers need to fetch objects from collections (Lists, Maps, Sets...) by some specific criteria. This is usualy performed iterating objects and comparing their properties to specific value.

To simplify this approach I wrote small framework which allows to select objects from collections (List in my case) using predecative conditions.

Lets imageine we have simple class QueryListTEST:

class QueryListTEST
{
boolean flag;
int value;
str nam;
}

public Boolean parmFlag(Boolean _flag = flag)
{
;
flag = _flag;
return flag;
}

public Str parmName(Str _name = nam)
{
;
if (!prmISDefault(_name))
{
nam = _name;
}
return nam;
}

public Int parmValue(Int _value = value)
{
;
if (!prmISDefault(_value))
{
value = _value;
}
return value;
}

public static QueryListTEST construct(str _name, boolean _flag, int _value)
{
QueryListTEST test = new QueryListTEST();
;
test.parmName(_name);
test.parmFlag(_flag);
test.parmValue(_value);
return test;
}


It contains 3 properties (parmMethods with 1 parameter with default value).
Lets initialize list of objects of type QueryListTEST:

QueryList list = new QueryList();
;
list.addEnd(QueryListTEST::construct("", true, 0));
list.addEnd(QueryListTEST::construct("2", false, -1));
list.addEnd(QueryListTEST::construct("3", true, 2));


QueryList class is basically what I'm going to implement in this example. It extends List but has one extra method select

found = list.select(new QueryListCondition_LessOrEq('parmFlag', false).and(new QueryListCondition_More('parmName', "2"));

found is also list of objects of the same type.
As you can see, list calls select method, passing condition to it.
Condition is represented by instance of class QueryListCondition.
It's an abstract class, which compares value of class instance property (first parameter in constructor) to some value (second parameter).
Its child classes implement all possible comparison boolean operators:
==, !=, >, >=, <, <= As you also can see, you can add several conditions, just calling methods of QueryListCondition.and(...) or QueryListCondition.or(...)

What happens inside is QueryListCondition adds condition, passed in methods and and or to its internal clhildConditon list and returns itself after method call.

Now let's see what happens inside QueryList.select:

public QueryList select(QueryListCondition _condition , boolean _firstonly = false)
{
ListEnumerator elements = this.getEnumerator();
QueryList foundList = new QueryList();
Object current;
;
if (_condition)
{
while (elements.moveNext())
{
current = elements.current();
if (_condition.result(current))
{
foundList.addEnd(current);
if (_firstonly)
{
break;
}
}
}
}
return foundList;
}


QueryList iterates through its elements and passes them to QueryListCondition for check of result

QueryListCondition itself checks its own result at first for particular object and results of all its inner conditions with regard to boolean operation and, or:

public boolean result(Object _object = null)
{
boolean res;
ListEnumerator conditionsEnumerator;
ListEnumerator operations;
QueryListCondition childCondition;
boolean andOr;
;
if (_object)
{
this.parmObject(_object);
}
res = this.ownResult();
if (childConditions)
{
conditionsEnumerator = childConditions.getEnumerator();
operations = childConditionsOp.getEnumerator();
while (conditionsEnumerator.moveNext() && operations.moveNext())
{
childCondition = conditionsEnumerator.current();
andOr = operations.current();
res = andOr ? res && childCondition.result(_object) : res childCondition.result(_object);
}
}
return res;
}


In QueryListCondition.ownResult condition fetches value from object property, validates it and returs proper result, according to condition type.

QueryListObject class represents object abstration for conditions. It takes Object as parameter in its construction and creates SysDictClass object according to Object's class.

public Types parmPropertyType(str _parmMethodName)
{
SysDictMethod method = new SysDictMethod(UtilElementType::Class, thisClass.id(), _parmMethodName);
;
return method.returnType();
}

public class QueryListObject
{
Object object;
SysDictClass thisClass;
}

public ClassId classId()
{
return thisClass.id();
}

protected void new()
{
}

public Object parmObject(Object _object = object)
{
if (!object && _object)
{
object = _object;
thisClass = new SysDictClass(classIdGet(object));
}
return object;
}

public anytype parmValue(str _parmMethodName)
{
anyType value;
;
if (thisClass.hasObjectMethod(_parmMethodName))
{
new ExecutePermission().assert();
//BP Deviation documented
value = thisClass.callObject(_parmMethodName, object);
}
return value;
}

public static QueryListObject newObject(object _object)
{
QueryListObject queryListObjet;
;
if (_object != null)
{
queryListObjet = new QueryListObject();
queryListObjet.parmObject(_object);
}
return queryListObjet;
}


Then when QueryListCondition fetches value from object using property name, it refers to QueryListObject to invoke property method:

public Anytype parmValue(str _propName = property)
{
;

if (_propName && queryListOBject)
{
value = queryListOBject.parmValue(_propName);
}
return value;
}


It's also possible to pass another property value as value to compare:

QueryListCondition prop2prop;
;
prop2prop = new QueryListCondition_Equal('parmName');
prop2prop.parmProp2Compare('parmFlag');

found = list.select(prop2prop);


However I faced some X++ reflection problems in this framework. When either property or value to compare is null (0, '', dateNull, conNull), there is comparison problem (stack trace), since anytype is used as return type in QueryListObject.value and system doesn't know how to cast type.

At first I relied on X++ reflection and wrote the next code:
QueryListCondition.ownResult looks like this:

protected boolean ownResult()
{
boolean ret;
anyType val = this.parmValue();
anyType val2Compare = this.parmValue2Compare();
;
if (typeOf(val) == typeOf(val2Compare) !val !val2Compare )
{
try
{
ret = this.thisResult(val,val2Compare);
}
catch
{
ret = false;
}
}

return ret;
}


Where method thisResult is abstrat. Let's see its implementation for >= condition:
QueryListCondition_MoreOrEq.thisResult:

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val >= _val2Compare;
}
return ret;
}


It checks, whether both of value and value to compare are set or not set. Depending on that it returns proper value.
It's easy to do with almost each type, but numeric types, such as Integer or Real because they can have negative values and that's why they not obviously compared to 0.
To solve thi problem class QueryListObject has method propertyType which is supposed to return property type using SysDictMethod.returnType(...) . Then, in QueryListCondition.thisResult method next code isexecuted:

ret = this.numericProperty() ? this.compareNumVal(_val) : true;

QueryListCondition.compareNumVal compares Real value to 0. So it also applies for Integer values.
The problem in this case is that SysDictMEthod.returnType always returns void.

But this can be solved by adding one extra parameter to QueryListCondition: propertyType.

This small framework provides developers with simple access to objects in collections without serious performance degradation. Now you don't have to program collection objects itrerative comparison with several if else statements and vast of status saving variables.

Enjoy :)

Be welcome to find code of QueryListCondition and all it ancestors below

QueryListCondition

protected boolean numericProperty()
{
return propertyType == Types::Integer propertyType == Types::Real;
}

protected abstract boolean compareNumVal(real _numVal)
{
}

protected abstract boolean thisResult(anytype _val, anytype _val2Compare)
{
}

public final QueryListCondition and(QueryListCondition _condition)
{
;
if (_condition)
{
childConditions.addEnd(_condition);
childConditionsOp.addEnd(NoYes::Yes);
}
return this;
}

abstract public class QueryListCondition
{
List childConditions;
List childConditionsOp;
anytype value;
anytype value2Compare;
str property, property2Compare;
Object object;
QueryListObject queryListObject;
Types propertyType;
}

void new(str _property = '', anytype _value2Compare = null, Types _propertyType = Types::AnyType)
{
if (_property)
{
this.parmProperty(_property);
}
if (_value2Compare)
{
this.parmValue2Compare(_value2Compare);
}
if (_propertyType != Types::AnyType)
{
propertyType = _propertyType;
}
childConditions = new List(Types::Class);
childConditionsOp = new List(Types::String);
}

public final QueryListCondition or(QueryListCondition _condition)
{
;
if (_condition)
{
childConditions.addEnd(_condition);
childConditionsOp.addEnd(NoYes::No);
}
return this;
}

protected boolean ownResult()
{
boolean ret;
anyType val = this.parmValue();
anyType val2Compare = this.parmValue2Compare();
;
if (typeOf(val) == typeOf(val2Compare) !val !val2Compare )
{
try
{
ret = this.thisResult(val,val2Compare);
}
catch
{
ret = false;
}
}
return ret;
}

public Object parmObject(Object _object = object)
{
;
if (!prmIsDefault(_object))
{
object = _object;
queryListObject = QueryListObject::newObject(object);
propertyType = propertyType == Types::AnyType propertyType == Types::void
? queryListObject.parmPropertyType(this.parmProperty())
: propertyType;
}
return object;
}

public str parmProp2Compare(str _prop2Compare = property2Compare)
{
;
property2Compare = _prop2Compare;
return property2Compare;
}

public str parmProperty(str _property = property)
{
;
property = _property;
return property;
}

public Anytype parmValue(str _propName = property)
{
;
if (_propName && queryListOBject)
{
value = queryListOBject.parmValue(_propName);
}
return value;
}

public Anytype parmValue2Compare(Anytype _value2Compare = value2Compare)
{
;
value2Compare = _value2Compare;
if (property2Compare && queryListOBject)
{
value2Compare = queryListOBject.parmValue(property2Compare);
}
return value2Compare;
}

public boolean result(Object _object = null)
{
boolean res;
ListEnumerator conditionsEnumerator;
ListEnumerator operations;
QueryListCondition childCondition;
boolean andOr;
;
if (_object)
{
this.parmObject(_object);
}
res = this.ownResult();
if (childConditions)
{
conditionsEnumerator = childConditions.getEnumerator();
operations = childConditionsOp.getEnumerator();
while (conditionsEnumerator.moveNext() && operations.moveNext())
{
childCondition = conditionsEnumerator.current();
andOr = operations.current();
res = andOr ? res && childCondition.result(_object) : res childCondition.result(_object);
}
}
return res;
}


QueryListCondition_Equal

public class QueryListCondition_Equal extends QueryListCondition
{
}


protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = false;
}
else if (!_val && _val2Compare)
{
ret = false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val == _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal == 0;
}


QueryListCondition_NotEqual

public class QueryListCondition_NotEqual extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret
;
if ((_val && !_val2Compare) (!_val && _val2Compare))
{
ret = true;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val == _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal != 0;
}

QueryListCondition_More

public class QueryListCondition_More extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val > _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal > 0;
}


QueryListCondition_MoreOrEq

public class QueryListCondition_MoreOrEq extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val >= _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal >= 0;
}

QueryListCondition_Less

public class QueryListCondition_Less extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : false;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : true;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val <>

protected boolean compareNumVal(real _numVal)
{
return _numVal <>

QueryListCondition_LessOrEq

public class QueryListCondition_LessOrEq extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : false;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : true;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val > _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal <= 0; }


QueryList

public class QueryList extends List
{
}

public void new(Types _type = Types::Class)
{
;
super(_type);
}

public QueryList select(QueryListCondition _condition , boolean _firstonly = false)
{
ListEnumerator elements = this.getEnumerator();
QueryList foundList = new QueryList();
Object current;
;
if (_condition)
{
while (elements.moveNext())
{
current = elements.current();
if (_condition.result(current))
{
foundList.addEnd(current);
if (_firstonly)
{
break;
}
}
}
}
return foundList;
}



No comments: