Dynamics AX - AIF, Using Asterix (wild card character) in an Criteria Element Find method
When consuming AIF services via c#, a common ask is can we write a query similar to a Like statement in SQL.
Statements like this one should be possible:
While you can use the '?' wildcard in the value, AX will strip any other special characters (specifically *",.()<>!= characters). This happens in the class SysQuery::value() method in AX.
One workaround, as found here, is to use the GreaterOrEqual operator. This essentially turns the query into a "starts with" statement.
It is a start, but not ideal. Well, turns out it isn't that difficult to enable the feature. After digging into the source code, the culprit method is getQueryValueForCriteriaElement in the class AxdBase in AX. Here we see a switch statement that handles all of the supported values for Operator (AifCriteriaOperator enum in AX). Notice the "Like" operator is conveniently missing.
Now, ideally we would add the missing value to the AifCriteriaOperator enum, and code the following statement (I am truly baffled why Microsoft excluded this):
However, in my testing, upon regenerating the AIF service and refreshing the service reference in C#, the new value is not created in the .NET class. If you try and cheat by casting a number outside of the enumeration's range (I used 200), upon serialization, the type is validated and throws an error, basically stating the number is not part of the enumeration.
For now, I'm out of time attempting to fix this "correctly", so without further ado, here is the final solution:
Now, as long as the Operation is Equals, the system will honor * wildcards as it should.
If anyone has any information on how .NET retrieves the schema for the above enumeration, I will update this post with a more elegant solution.
Statements like this one should be possible:
(C#)
var criteriaElements = new AXCSICustomer.CriteriaElement[1];
criteriaElements[0] = new AXCSICustomer.CriteriaElement();
criteriaElements[0].DataSourceName = "DirParty";
criteriaElements[0].FieldName = "Name";
criteriaElements[0].Operator = AXCSICustomer.Operator.Equal;
criteriaElements[0].Value1 = "*bob*
";
var queryCritieria = new AXCSICustomer.QueryCriteria()
{
CriteriaElement = criteriaElements
};
var criteriaElements = new AXCSICustomer.CriteriaElement[1];
criteriaElements[0] = new AXCSICustomer.CriteriaElement();
criteriaElements[0].DataSourceName = "DirParty";
criteriaElements[0].FieldName = "Name";
criteriaElements[0].Operator = AXCSICustomer.Operator.Equal;
criteriaElements[0].Value1 = "*bob*
var queryCritieria = new AXCSICustomer.QueryCriteria()
{
CriteriaElement = criteriaElements
};
While you can use the '?' wildcard in the value, AX will strip any other special characters (specifically *",.()<>!= characters). This happens in the class SysQuery::value() method in AX.
One workaround, as found here, is to use the GreaterOrEqual operator. This essentially turns the query into a "starts with" statement.
(C#)
criteriaElements[0].Operator = AXCSICustomer.Operator.GreaterThanOrEqual;
criteriaElements[0].Value1 = "bob";
//Matches Bobby, Bob, and Bobsledders, but not Kabobs
criteriaElements[0].Operator = AXCSICustomer.Operator.GreaterThanOrEqual;
criteriaElements[0].Value1 = "bob";
//Matches Bobby, Bob, and Bobsledders, but not Kabobs
It is a start, but not ideal. Well, turns out it isn't that difficult to enable the feature. After digging into the source code, the culprit method is getQueryValueForCriteriaElement in the class AxdBase in AX. Here we see a switch statement that handles all of the supported values for Operator (AifCriteriaOperator enum in AX). Notice the "Like" operator is conveniently missing.
(X++)
private str getQueryValueForCriteriaElement(AifCriteriaElement _criteriaElement, SysDictField _dictField = null)
{
str queryValue;
anytype value1, value2;
if (_dictField == null)
{
value1 = _criteriaElement.getValue1();
}
else
{
value1 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue1());
}
switch (_criteriaElement.getOperator())
{
case AifCriteriaOperator::Equal:
queryValue = queryValue(value1);
break;
case AifCriteriaOperator::NotEqual:
queryValue = queryNotValue(value1);
break;
case AifCriteriaOperator::Range:
if (_dictField == null)
{
value2 = _criteriaElement.getValue2();
}
else
{
value2 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue2());
}
queryValue = queryRange(value1, value2);
break;
case AifCriteriaOperator::GreaterOrEqual:
queryValue = queryRange(value1, SysQuery::valueUnlimited());
break;
case AifCriteriaOperator::Greater:
queryValue = strFmt('>%1', queryValue(value1));
break;
case AifCriteriaOperator::LessOrEqual:
queryValue = queryRange(SysQuery::valueUnlimited(), value1);
break;
case AifCriteriaOperator::Less:
queryValue = strFmt('<%1', queryValue(value1));
break;
}
return queryValue;
}
private str getQueryValueForCriteriaElement(AifCriteriaElement _criteriaElement, SysDictField _dictField = null)
{
str queryValue;
anytype value1, value2;
if (_dictField == null)
{
value1 = _criteriaElement.getValue1();
}
else
{
value1 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue1());
}
switch (_criteriaElement.getOperator())
{
case AifCriteriaOperator::Equal:
queryValue = queryValue(value1);
break;
case AifCriteriaOperator::NotEqual:
queryValue = queryNotValue(value1);
break;
case AifCriteriaOperator::Range:
if (_dictField == null)
{
value2 = _criteriaElement.getValue2();
}
else
{
value2 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue2());
}
queryValue = queryRange(value1, value2);
break;
case AifCriteriaOperator::GreaterOrEqual:
queryValue = queryRange(value1, SysQuery::valueUnlimited());
break;
case AifCriteriaOperator::Greater:
queryValue = strFmt('>%1', queryValue(value1));
break;
case AifCriteriaOperator::LessOrEqual:
queryValue = queryRange(SysQuery::valueUnlimited(), value1);
break;
case AifCriteriaOperator::Less:
queryValue = strFmt('<%1', queryValue(value1));
break;
}
return queryValue;
}
Now, ideally we would add the missing value to the AifCriteriaOperator enum, and code the following statement (I am truly baffled why Microsoft excluded this):
(X++)
case AifCriteriaOperator::Like:
queryValue = SysQuery::valueLike(value1);
break;
case AifCriteriaOperator::Like:
queryValue = SysQuery::valueLike(value1);
break;
However, in my testing, upon regenerating the AIF service and refreshing the service reference in C#, the new value is not created in the .NET class. If you try and cheat by casting a number outside of the enumeration's range (I used 200), upon serialization, the type is validated and throws an error, basically stating the number is not part of the enumeration.
For now, I'm out of time attempting to fix this "correctly", so without further ado, here is the final solution:
(X++)
private str getQueryValueForCriteriaElement(AifCriteriaElement _criteriaElement, SysDictField _dictField = null)
{
str queryValue;
anytype value1, value2;
if (_dictField == null)
{
value1 = _criteriaElement.getValue1();
}
else
{
value1 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue1());
}
switch (_criteriaElement.getOperator())
{
case AifCriteriaOperator::Equal:
queryValue = queryValue(value1);
//<coffeestain>
//Allow like queries via aif
if(strContains(value1, "*"))
{
queryValue = value1;
}
//</coffeestain>
break;
case AifCriteriaOperator::NotEqual:
queryValue = queryNotValue(value1);
break;
case AifCriteriaOperator::Range:
if (_dictField == null)
{
value2 = _criteriaElement.getValue2();
}
else
{
value2 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue2());
}
queryValue = queryRange(value1, value2);
break;
case AifCriteriaOperator::GreaterOrEqual:
queryValue = queryRange(value1, SysQuery::valueUnlimited());
break;
case AifCriteriaOperator::Greater:
queryValue = strFmt('>%1', queryValue(value1));
break;
case AifCriteriaOperator::LessOrEqual:
queryValue = queryRange(SysQuery::valueUnlimited(), value1);
break;
case AifCriteriaOperator::Less:
queryValue = strFmt('<%1', queryValue(value1));
break;
case AifCriteriaOperator::Like:
queryValue = SysQuery::valueLike(value1);
break;
}
return queryValue;
}
private str getQueryValueForCriteriaElement(AifCriteriaElement _criteriaElement, SysDictField _dictField = null)
{
str queryValue;
anytype value1, value2;
if (_dictField == null)
{
value1 = _criteriaElement.getValue1();
}
else
{
value1 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue1());
}
switch (_criteriaElement.getOperator())
{
case AifCriteriaOperator::Equal:
queryValue = queryValue(value1);
//<coffeestain>
//Allow like queries via aif
if(strContains(value1, "*"))
{
queryValue = value1;
}
//</coffeestain>
break;
case AifCriteriaOperator::NotEqual:
queryValue = queryNotValue(value1);
break;
case AifCriteriaOperator::Range:
if (_dictField == null)
{
value2 = _criteriaElement.getValue2();
}
else
{
value2 = AifUtil::getTypedValue(_dictField, _criteriaElement.getValue2());
}
queryValue = queryRange(value1, value2);
break;
case AifCriteriaOperator::GreaterOrEqual:
queryValue = queryRange(value1, SysQuery::valueUnlimited());
break;
case AifCriteriaOperator::Greater:
queryValue = strFmt('>%1', queryValue(value1));
break;
case AifCriteriaOperator::LessOrEqual:
queryValue = queryRange(SysQuery::valueUnlimited(), value1);
break;
case AifCriteriaOperator::Less:
queryValue = strFmt('<%1', queryValue(value1));
break;
case AifCriteriaOperator::Like:
queryValue = SysQuery::valueLike(value1);
break;
}
return queryValue;
}
Now, as long as the Operation is Equals, the system will honor * wildcards as it should.
If anyone has any information on how .NET retrieves the schema for the above enumeration, I will update this post with a more elegant solution.
Comments
Post a Comment