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
selectfound = 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
resultQueryListCondition 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
QueryListConditionprotected 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_Equalpublic 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_NotEqualpublic 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_Morepublic 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_MoreOrEqpublic 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_Lesspublic 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;
}